Common mistakes
The mistakes that cause most bugs and holes with cryptography, each with a wrong and a right version.
These are the mistakes that cause most bugs and security holes. Each one has a wrong version and a right version, and links to the page that covers it.
Reusing a nonce
-- Wrong: the same nonce for every message gives away what they say
local Nonce: buffer = buffer.create(12)
local Ciphertext: buffer, Tag: buffer = AEAD.Encrypt(Message, Key, Nonce)-- Right: a new random nonce every time, sent with the ciphertext
local Nonce: buffer = CSPRNG.RandomBytes(24)
local Ciphertext: buffer, Tag: buffer = AEAD.Encrypt(Message, Key, Nonce, nil, nil, true)See Encryption.
Using what Decrypt gave without checking it
-- Wrong: Decrypt gives nil when the data was changed, and this errors, or worse
local Plaintext = AEAD.Decrypt(Ciphertext, Key, Nonce, Tag)
local Data = HttpService:JSONDecode(buffer.tostring(Plaintext))-- Right: check for nil first
local Plaintext: buffer? = AEAD.Decrypt(Ciphertext, Key, Nonce, Tag)
if not Plaintext then
return -- changed, or the wrong key
end
local Data = HttpService:JSONDecode(buffer.tostring(Plaintext))See Encryption.
Comparing MACs with ==
-- Wrong: two buffers are only == when they're the same buffer, so this is always false
local _, Expected: buffer = Hashing.HMAC(Message, Key, Hashing.SHA2.SHA256, 64)
if Expected == ReceivedMac then
Accept(Message)
end-- Right: compare the bytes, in constant time
local _, Expected: buffer = Hashing.HMAC(Message, Key, Hashing.SHA2.SHA256, 64)
if Utilities.CtCompare(Expected, ReceivedMac) then
Accept(Message)
endComparing the hex strings with == works, but it stops at the first character that differs, which tells an attacker how much of their guess was right. See Message authentication.
Using the hex where a buffer goes
-- Wrong: a hash gives the hex string first, and that's what Key gets
local Key = Hashing.SHA2.SHA256(Secret)
AEAD.Encrypt(Message, Key, Nonce) -- errors: Key must be a buffer-- Right: take the second value, the buffer
local _, Key: buffer = Hashing.SHA2.SHA256(Secret)See Hashing.
Making keys with math.random
-- Wrong: someone who sees a few math.random numbers can work out the rest
local Key: buffer = buffer.create(32)
for Index = 0, 31 do
buffer.writeu8(Key, Index, math.random(0, 255))
end-- Right
local Key: buffer = CSPRNG.RandomBytes(32)See Random numbers.
Using a shared secret as a key
-- Wrong: the shared secret isn't tied to this exchange or this job
local SharedSecret: buffer = X25519.Exchange(SecretKey, TheirPublicKey)
AEAD.Encrypt(Message, SharedSecret, Nonce)-- Right: derive a key from it, with both public keys mixed in
local SharedSecret: buffer = X25519.Exchange(SecretKey, TheirPublicKey)
local _, SessionKey: buffer = DeriveSessionKey(Concatenate(SharedSecret, ClientPublicKey, ServerPublicKey), 32)
AEAD.Encrypt(Message, SessionKey, Nonce)See Key exchange.
Keeping a secret key on the client
-- Wrong: a LocalScript, or a module in ReplicatedStorage, is readable by every client
local SIGNING_KEY: buffer = buffer.fromstring("my super secret key 1234567890ab")Anything a client can see, an exploiter can read. Keep secret keys on the server, in ServerScriptService or ServerStorage, and only send clients public keys.
Using MD5 or SHA-1 for security
-- Wrong: someone can make a second file with the same MD5
local Hex: string = Hashing.MD5(SaveData)-- Right
local Hex: string = Hashing.SHA2.SHA256(SaveData)See Hashing.