Encrypting string to Sha1 in Homeyscript

Hi,
Anyone knows how to encrypt a string variable to Sha1?
When encrypting to Base64 I use .toString(‘base64’), so wonder if there are anything similar available for Sha1 encryption?
Hope someone can guide me :slight_smile:
Thx in advance

const crypto = (this.constructor.constructor('return process.mainModule.require')())('crypto');

function hash(string) {
  const hasher = crypto.createHash('sha1');
  hasher.update(string);
  return hasher.digest('hex');
}

console.log( hash('hello world') )

Of course, SHA1 isn’t an encryption algorithm but a hash function (and Base64 is an encoding method) :wink:

It worked :slight_smile:

First thank you - you saved my day.
Second thanks for super fast reply…
and third, you learned me something new today :slight_smile:

Hi,
I have been using the same code to hash some information, but since a couple days Homeyscript gives an error on the crypto library.

This piece: const crypto = (this.constructor.constructor('return process.mainModule.require')())('crypto');
does not work anymore:

TypeError: Cannot read properties of undefined (reading 'require')

I have rebooted the Homey Pro and rebooted the Homeyscript app without success. Also checked if I have the latest Homey Script version, which i do.

I hope someone (I’m looking at you @robertklep :joy: ) has a solution for this :frowning:

Looks like process.mainModule was dropped from Node.js (either because it’s been deprecated for a while now, or Athom has removed it intentionally).

If you need to calculate an SHA1 hash, the only solution I can think of right now is to add a pure-JS implementation of the SHA1 algorithm to your script:

const hs = Array.from(Array(16), (_, i) => i.toString(16));
const hsr = hs.slice().reverse();
const h2s =  hs.join("").match(/../g), h2sr = hsr.join("").match(/../g);
const h2mix = hs.map((h, i) => `${hsr[i]}${h}`);
const hseq = h2s.concat(h2sr, h2mix).map(hex => parseInt(hex, 16));
const H = new Uint32Array(Uint8Array.from(hseq.slice(0, 20)).buffer);
const K = Uint32Array.from(
    [2, 3, 5, 10], v => Math.floor(Math.sqrt(v) * (2 ** 30)));
const F = [
    (b, c, d) => ((b & c) | ((~b >>> 0) & d)) >>> 0,
    (b, c, d) => b ^ c ^ d,
    (b, c, d) => (b & c) | (b & d) | (c & d),
    (b, c, d) => b ^ c ^ d,    
];
function rotl(v, n) {
    return ((v << n) | (v >>> (32 - n))) >>> 0;
}

function sha1(buffer) {
    const u8a = ArrayBuffer.isView(buffer) ?
          new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength) :
          new Uint8Array(buffer);
    const total = Math.ceil((u8a.length + 9) / 64) * 64;
    const chunks = new Uint8Array(total);
    chunks.set(u8a);
    chunks.fill(0, u8a.length);
    chunks[u8a.length] = 0x80;
    const lenbuf = new DataView(chunks.buffer, total - 8);
    const low = u8a.length % (1 << 29);
    const high = (u8a.length - low) / (1 << 29);
    lenbuf.setUint32(0, high, false);
    lenbuf.setUint32(4, low << 3, false);
    
    const hash = H.slice();
    const w = new Uint32Array(80);
    for (let offs = 0; offs < total; offs += 64) {
        const chunk = new DataView(chunks.buffer, offs, 64);
        for (let i = 0; i < 16; i++) w[i] = chunk.getUint32(i * 4, false);
        for (let i = 16; i < 80; i++) {
            w[i] = rotl(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
        }
        let [a, b, c, d, e] = hash;
        for (let s = 0; s < 4; s++) {
            for (let i = s * 20, end = i + 20; i < end; i++) {
                const ne = rotl(a, 5) + F[s](b, c, d) + e + K[s] + w[i];
                [a, b, c, d, e] = [ne >>> 0, a, rotl(b, 30), c, d];
            }
        }
        hash[0] += a; hash[1] += b; hash[2] += c; hash[3] += d; hash[4] += e;
    }
    const digest = new DataView(new ArrayBuffer(20));
    hash.forEach((v, i) => digest.setUint32(i * 4, v, false));
    return Buffer.from(digest.buffer).toString('hex');
}

(source)

Usage example:

const hash = sha1('some input string');
console.log( hash );

Thank you very much!

I gave the code a try and found that it was not working as expected. The hash returned was for a blank value so whatever you passed would give the same results. With a bit of playing I found the above secion of code needed to be changed for it to work. Once I made the adjustment I tested the rest of the code against other sha1 hash generators and its working perfectly now:

const u8a = new Uint8Array(buffer.length);
for(i=0;i<buffer.length;i+=1) {
  u8a[i]=buffer.codePointAt(i);
}

Ah yes, the code assumes the input data is passed as a buffer, which won’t be the case with Homey.

In that case, a faster fix would be:

const u8a = ArrayBuffer.isView(buffer) ?
        new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength) :
        new Uint8Array(Buffer.from(buffer));

Thankyou very much - this really helps :slight_smile: