Initial version: 2024-02-14
Last update: 2024-04-20
11000101100000011100001110110011011001001100010110111010
is still the same indistinguishable string of bits (saying "indistinguishable" I mean that you have problem to say what does this string represent; in fact this is a name of my city, Łódź, encoded in UTF-8) no matter if you store it in RAM, SSD or Micro SD card. Moreover, if someone instead writes:
11000101100000011100001110110011011000111100010110111010
looking at the second string and not being able to compare it with the first one, you can't tell if something has changed part of it or not (can you point out the difference between them?).
[IMG: signature_sign_data]
[IMG: signature_verify_signature]
messageText = "Test message"
data = {"message": messageText,
"timestamp": f"{datetime.datetime.now()}"}
dataString = json.dumps(data, sort_keys=True)
dataBytes = dataString.encode('utf-8')
dataBytesB64 = base64.b64encode(dataBytes).decode("utf-8")
print(dataBytesB64)
When executed you get the readable string you can attach to email or send with any other method you want:
eyJtZXNzYWdlIjogIlRlc3QgbWVzc2FnZSIsICJ0aW1lc3RhbXAiOiAiMjAyNC0wNC0yMCAxMToxNDo1MC43NjU4MTgifQ==
Prepare key
key = nacl.utils.random(nacl.secret.SecretBox.KEY_SIZE)
keyB64 = base64.b64encode(key).decode("utf-8")
print(keyB64)
Again thanks to Base64 coding you can easily operate with this key which is represented as an ASCII text:
+uJq2y3rPvnLcbIXvUXYmruRObEDqltXxoD/1a1Z3SE=
Signing data
'''
data - data as a string encoded in Base64
key - key encoded in Base64
'''
def sign(dataB64, keyB64):
signature = {}
# Hash data
dataBytes = dataB64.encode('utf-8')
hashObj = hashlib.sha256(dataBytes)
hashBytes = hashObj.digest()
hashHex = hashObj.hexdigest()
hashB64 = base64.b64encode(hashBytes).decode("utf-8")
# Encrypt hash
keyBytes = base64.b64decode(keyB64.encode('utf-8'))
box = nacl.secret.SecretBox(keyBytes)
hashEncrypted = box.encrypt(hashBytes)
hashEncryptedB64 = base64.b64encode(hashEncrypted).decode("utf-8")
signature = {
"hashHex": hashHex,
"hashB64": hashB64,
"signature": hashEncryptedB64
}
return signature
As arguments of a sign()
function you pass previously prepared data: data you want to sign and key you want to use to protect your signature, both encoded in Base64 coding:
signature = sign(dataBytesB64, keyB64)
This way you obtain your signature (which is an encrypted hash):
{
'hashHex': 'b4094125e5501af3cfa98673c8aefcccb4e8406df1274f8812ebdb6a8561d729',
'hashB64': 'tAlBJeVQGvPPqYZzyK78zLToQG3xJ0+IEuvbaoVh1yk=',
'signature': 'yX6IV0gR7VudAIgDMTIMTzkMSoPblCg8STFi8Ie0B3mPrFYzB98PZR83OSu5r69ziIjKDPYAUg0YQ/m24ScNoZKJUt72Q40G'
}
Your signature, string yX6...40G
, is in Base64 coding so you can easily attach it to your data.
def verifySignature(dataB64, signatureB64, keyB64):
# Hash data
dataBytes = dataB64.encode('utf-8')
hashObj = hashlib.sha256(dataBytes)
hashBytes = hashObj.digest()
hashHex = hashObj.hexdigest()
hashB64 = base64.b64encode(hashBytes).decode("utf-8")
# Decrypt hash
keyBytes = base64.b64decode(keyB64.encode('utf-8'))
box = nacl.secret.SecretBox(keyBytes)
signatureBytes = base64.b64decode(signatureB64.encode('utf-8'))
hashDecrypted = box.decrypt(signatureBytes)
hashDecryptedB64 = base64.b64encode(hashDecrypted).decode("utf-8")
if hashB64 == hashDecryptedB64:
return True
return False
As arguments of a verifySignature()
function you pass three data: two of them are the same as for sign()
function and one is a signature (also encoded in Base64 coding):
result = verifySignature(dataBytesB64, signature["signature"], keyB64)
print(result)
If signature is correct you will get True
, otherwise you will get False
:
True
import nacl.encoding
import nacl.signing
Signatory person generates its private and public key:
signerPrivateKey = nacl.signing.SigningKey.generate()
signerPublicKey = signerPrivateKey.verify_key
Since signerPublicKey
is a sequence of bytes, signatory person may need to transform it to a readable format before publishing it. You can encode it in hex, Base64 or any other form useful for you:
signerPublicKeyHex = signerPublicKey.encode(encoder=nacl.encoding.HexEncoder)
print(signerPublicKeyHex)
signerPublicKeyBase64 = signerPublicKey.encode(encoder=nacl.encoding.Base64Encoder)
print(signerPublicKeyBase64.decode('utf-8'))
In my case, the public key in hex and Base64 encoding takes the form:
b'b75ee7520ac021d127fedb44108835c3dc753dd9f1784bf237389ac553606126'
t17nUgrAIdEn/ttEEIg1w9x1PdnxeEvyNziaxVNgYSY=
Now signatory person is ready to sign message:
messageText = "Test text"
messageTextBytes = messageText.encode('utf-8')
# Signing a message encoded with Base64Encoder
messageSignedBase64 = signerPrivateKey.sign(messageTextBytes, encoder = nacl.encoding.Base64Encoder)
print(messageSignedBase64.decode('utf-8'))
# Signing a message without encoding the key or message
messageSigned = signerPrivateKey.sign(messageTextBytes)
print(messageSigned)
SMn7wqpQ2hWS3zl/Uj691UqxFAlUgMxyO1bjtvOXSD+zctjDRx8KemEEylCsjaWUb4tdzI73/zmF8zYXDQePDFRlc3QgdGV4dA==
b'H\xc9\xfb\xc2\xaaP\xda\x15\x92\xdf9\x7fR>\xbd\xd5J\xb1\x14\tT\x80\xccr;V\xe3\xb6\xf3\x97H?\xb3r\xd8\xc3G\x1f\nza\x04\xcaP\xac\x8d\xa5\x94o\x8b]\xcc\x8e\xf7\xff9\x85\xf36\x17\r\x07\x8f\x0cTest text'
As you can see by printing messageSigned
, the message is not encrypted after signing but it is padded with bytes containing the signature.
signerPublicKeyHex
(or signerPublicKeyBase64
) to verify that the message was signed by its owner:
import nacl.encoding
import nacl.signing
Create a "box" for veryfying signature:
verifyKey = nacl.signing.VerifyKey(signerPublicKeyBase64, encoder=nacl.encoding.Base64Encoder)
Verify the signature of the message:
signedText = verifyKey.verify(messageSignedBase64, encoder=nacl.encoding.Base64Encoder)
print(signedText.decode('utf-8'))
If everything is correct, you will see signed text:
Test text
Of course, for private key encoded in Base64 you can do the same steps:
verifyKey = nacl.signing.VerifyKey(signerPublicKeyHex, encoder=nacl.encoding.HexEncoder)
signedText = verifyKey.verify(messageSignedBase64, encoder=nacl.encoding.Base64Encoder)
print(signedText.decode('utf-8'))
with the same result:
Test text