Fingerprints: hashes
A hash turns any amount of data into a short fingerprint of fixed size. The same data always gives the same hash, and the smallest change gives a completely different one, so a hash tells you whether a file arrived intact or has changed since you last saw it. messageDigest makes one with the algorithm you name; the result is bytes, best shown in hexadecimal (Binary data):
function sha256Hex pText
local tHex
get binaryDecode("H*", messageDigest(textEncode(pText, "UTF-8"), "SHA-256"), tHex)
return tHex
end sha256Hex
put sha256Hex("hello")
put sha256Hex("hello!")
put the length of sha256Hex("a much, much longer piece of text than the others")
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 ce06092fb948d9ffac7d1a376e404b26b7575bcc11ee05a4615fef4fec3a308b 64
Hash text by its UTF-8 bytes, as here, so the same text gives the same hash on every system. "SHA-256", "SHA-512" and "SHA3-256" are good choices today.
Signing a message: HMAC
A hash shows that data changed, but anyone can compute one, so an attacker who changes a message can change its hash too. An HMAC mixes a secret key into the hash: only someone with the key can make the right code, so a message that arrives with a matching code came from someone who knows the key, unchanged. Many web services sign their messages this way:
function signHex pMessage, pKey
local tHex
get binaryDecode("H*", messageAuthenticationCode(pMessage, pKey, "HMAC-SHA-256"), tHex)
return tHex
end signHex
put signHex("pay Ada 10", "our shared secret") into tCode
put the length of tCode
put signHex("pay Ada 10", "our shared secret") is tCode
put signHex("pay Ada 1000", "our shared secret") is tCode
put signHex("pay Ada 10", "a guessed secret") is tCode
64 true false false
Random enough to be secret
random is fine for games, but its numbers can be predicted, which makes them useless for anything secret. randomBytes takes bytes from the system's secure source, the right way to make a key, a token or a salt. uuid makes a unique ID, 36 characters, safe to use as the name of a record or a file without asking a server for one:
get binaryDecode("H*", randomBytes(16), tToken)
put "a token:" && tToken
put "an ID:" && uuid()
a token: 3f9c0e51a2b7d48c6e01f5a9b3d27c84 an ID: 1b4e28ba-2fa1-4d3b-a3f5-ef19b5a7633b
Both lines differ every run, which is the point.
Storing passwords
Never store a password, and never store a plain hash of one: computers can try billions of guesses at a SHA-256 hash every second. A password needs a hash made to be slow and to use a lot of memory, with a random salt so that two people with the same password get different hashes. OXT-Beyond comes with libsodium, through the SodiumXT extension, whose sxPwHashStr does all of this with Argon2id and gives back one line of text to store. sxPwHashStrVerify checks a password typed at login against it:
function hashPassword pPassword
return textDecode(sxPwHashStr(textEncode(pPassword, "UTF-8"), "2", sxPwMemInteractive()), "UTF-8")
end hashPassword
function passwordMatches pPassword, pStored
return sxPwHashStrVerify(pStored, textEncode(pPassword, "UTF-8"))
end passwordMatches
put hashPassword("correct horse battery") into tStored
put word 1 of tStored begins with "$argon2id$"
put hashPassword("correct horse battery") is tStored
put passwordMatches("correct horse battery", tStored)
put passwordMatches("correct horse batter", tStored)
true false true false
Hashing the same password twice gives two different lines, because each has its own salt, and both still match. The line records the salt and the settings, so it is all you store.
Encrypting
To keep data secret but get it back later, encrypt it with a key. libsodium's sxSecretBox encrypts and also seals the data, so any change to it, or the wrong key, is refused rather than giving nonsense:
put sxRandomBytes(32) into tKey
put sxSecretBox(textEncode("meet at noon", "UTF-8"), tKey) into tBox
put the number of bytes of tBox && "bytes, not readable"
put textDecode(sxSecretBoxOpen(tBox, tKey), "UTF-8")
try
get sxSecretBoxOpen(tBox, sxRandomBytes(32))
put "opened with the wrong key?"
catch tError
put "the wrong key is refused"
end try
52 bytes, not readable meet at noon the wrong key is refused
The key is 32 random bytes. Keep it as safe as the data: anyone who has it can open the box. To make a key from a password instead, see sxPwHash in the Dictionary.
The engine's own encrypt and decrypt commands use OpenSSL, with any cipher of the cipherNames and a password or key. They suit data that another program will decrypt with the same cipher:
encrypt "meet at noon" using "aes-256-cbc" with password "a long passphrase"
put it into tSecret
put tSecret is not "meet at noon"
decrypt tSecret using "aes-256-cbc" with password "a long passphrase"
put it
decrypt tSecret using "aes-256-cbc" with password "a wrong guess"
put "with the wrong password:" && word 1 to -1 of the result
true meet at noon with the wrong password: (SSL error: bad decrypt)
Try it yourself
Write a function that says whether a file has changed: it keeps the SHA-256 hash of each file it has seen in a script local, and returns new, same or changed. Write a file, check it, check it again, change it, and check it once more.
Show a solution
local sSeen
function fileStatus pPath
local tHash, tStatus
put messageDigest(URL ("binfile:" & pPath), "SHA-256") into tHash
if sSeen[pPath] is empty then
put "new" into tStatus
else if sSeen[pPath] is tHash then
put "same" into tStatus
else
put "changed" into tStatus
end if
put tHash into sSeen[pPath]
return tStatus
end fileStatus
put specialFolderPath("temporary") & "/oxt-lesson-watch.txt" into tPath
put "version 1" into URL ("file:" & tPath)
put fileStatus(tPath)
put fileStatus(tPath)
put "version 2" into URL ("file:" & tPath)
put fileStatus(tPath)
delete file tPath
new same changed
The file is read with binfile:, so the hash is of its exact bytes.
Words in this lesson
messageDigestmessageAuthenticationCoderandomBytesuuidencryptdecryptthe cipherNamessxPwHashStrsxPwHashStrVerifysxSecretBoxsxSecretBoxOpensxRandomBytesEach one finds every lesson that uses it. The Dictionary has the whole story of each, with its syntax and more examples.