6

I need to encrypt strings with TEXT input, 1 round, HEX output, SHA-256 encryption. Which should be a string of characters of length 64.
Every SHA-256 encryption module I've tried in Google Apps Script docs returns a set of numbers. For example.

function SHA256() {
    var signature = Utilities.computeHmacSha256Signature("this is my input",
                                                 "my key - use a stronger one",
                                                 Utilities.Charset.US_ASCII);
Logger.log(signature);
    }

Outputs

[53, -75, -52, -25, -47, 86, -21, 14, -2, -57, 5, -13, 24, 105, -2, -84, 127, 115, -40, -75, -93, -27, -21, 34, -55, -117, -36, -103, -47, 116, -55, -61]

I haven't seen anything in the docs or elsewhere that specifies every parameter I'm going for outlined above for GAS. I wouldn't mind a deeper explanation of putting it together from scratch if that is what is required. I'm encrypting info to send to Facebook for Offline Conversions for ads. How does Facebook decrypt the encrypted strings?
Google Apps Script docs
https://developers.google.com/apps-script/reference/utilities/utilities#computeHmacSha256Signature(String,String,Charset)

Hugh Mungus
  • 129
  • 1
  • 9
  • [Edit] to show sample input and output – TheMaster Dec 17 '19 at 21:14
  • Is SHA-256, Hex, 1 round, Text input written out somewhere in a function in Javascript that I can just copy and paste into my script? This function, for example https://geraintluff.github.io/sha256/ ? I'm really not even sure how this works, how the receiver decrypts the string tbh. – Hugh Mungus Dec 17 '19 at 21:17
  • That's what it outputs. What do you expect it to output? What's the expected result for a given input? – TheMaster Dec 17 '19 at 21:23
  • "What do you expect it to output?" A string of characters of length 64. – Hugh Mungus Dec 17 '19 at 21:25

2 Answers2

10

̶U̶t̶i̶l̶i̶t̶i̶e̶s̶.̶c̶o̶m̶p̶u̶t̶e̶H̶m̶a̶c̶S̶h̶a̶2̶5̶6̶S̶i̶g̶n̶a̶t̶u̶r̶e̶ Utilities.computeDigest()returns an array of bytes (8-bit integers). If you want to convert that array to a string composed of hexadecimal characters you'll have to do it manually as follows:

/** @type Byte[] */
var signature = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, value);

/** @type String */
var hexString = signature
    .map(function(byte) {
        // Convert from 2's compliment
        var v = (byte < 0) ? 256 + byte : byte;

        // Convert byte to hexadecimal
        return ("0" + v.toString(16)).slice(-2);
    })
    .join("");
TheAddonDepot
  • 8,408
  • 2
  • 20
  • 30
  • Thank you. This is a dumb question, but how does my receiver of this, the person I send the encrypted string to, how do they decrypt the string? How do they get the key? – Hugh Mungus Dec 17 '19 at 21:26
  • You would also need to prepad each byte with `0`, if `toString(16)` returns a single digit instead of a double. Also, the byte array values are signed(+-). It needs to be unsigned – TheMaster Dec 17 '19 at 21:29
  • @Hugh Signature is a one way hash. You can't decrypt it – TheMaster Dec 17 '19 at 21:32
  • This answer isn't quite giving me what I get from the jsSHA("SHA-256", "TEXT", 1) npm module. A dozen or so dashes in your answer's output. – Hugh Mungus Dec 17 '19 at 21:41
  • @TheMaster I'm just trying to follow the instructions here to send data to Facebook. I'm not sure what I'm missing, but I'm sure they don't want random strings rather than email addresses. https://developers.facebook.com/ads/blog/post/2018/05/29/send-offline-conversions/ – Hugh Mungus Dec 17 '19 at 21:43
  • @Hugh it's not random. It's a signature. It's proof that you know the email address. Facebook also knows the address and the Facebook's hash and your hash will be the same. But anyone who hacks the message in between communication cannot decipher the actual address from the hash. – TheMaster Dec 17 '19 at 21:53
  • @TheMaster I see now. A hash of 'abc' is always the same string for anyone hashing this way so the person on the other end can compare to their hashes. I've ended up using the long JS function in this link since it gives me what I want. https://geraintluff.github.io/sha256/ – Hugh Mungus Dec 17 '19 at 21:56
  • @HughMungus If you just needed to generate an SHA-256 hash then use `Utilities.computeDigest()` instead. Updating the script (again). – TheAddonDepot Dec 17 '19 at 22:04
  • FYI for those coming here trying to SHA256 hash user info to send to Facebook Custom audiences, this answer is the way to go. Thank you @TheAddonDepot – MarketerInCoderClothes May 26 '22 at 08:06
1

Here's a pair of functions to generate a SHA-256 hash as a hex string:

function Sha256Hash(value) {
  return BytesToHex(
    Utilities.computeDigest(
      Utilities.DigestAlgorithm.SHA_256, value));
}

function BytesToHex(bytes) {
  let hex = [];
  for (let i = 0; i < bytes.length; i++) {
    let b = parseInt(bytes[i]);
    if (b < 0) {
      c = (256+b).toString(16);
    } else {
      c = b.toString(16);
    }
    if (c.length == 1) {
      hex.push("0" + c);
    } else {
      hex.push(c);
    }
  }
  return hex.join("");
}

It turns out that the bytes[] type is usable as a normal Array and so I took advantage of that when writing BytesToHex().

There must be more elegant ways but I took this a bit further and managed to derive a correct signature for calling the AWS API:

// Google Apps Script version of https://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-javascript
function getSignatureKey(key, dateStamp, regionName, serviceName) {
  const kDate = Utilities.computeHmacSha256Signature(dateStamp, "AWS4" + key);
  Logger.log("kDate    = '" + BytesToHex(kDate) + "'");
  const kRegion = Utilities.computeHmacSha256Signature(Utilities.newBlob(regionName).getBytes(), kDate);
  Logger.log("kRegion  = '" + BytesToHex(kRegion) + "'");
  const kService = Utilities.computeHmacSha256Signature(Utilities.newBlob(serviceName).getBytes(), kRegion);
  Logger.log("kService = '" + BytesToHex(kService) + "'");
  const kSigning = Utilities.computeHmacSha256Signature(Utilities.newBlob("aws4_request").getBytes(), kService);
  Logger.log("kSigning = '" + BytesToHex(kSigning) + "'");
  return kSigning;
}

function Sha256Hmac(value, key) {
  return BytesToHex(Utilities.computeHmacSha256Signature(Utilities.newBlob(value).getBytes(), key));
}
Neil C. Obremski
  • 18,696
  • 24
  • 83
  • 112