Skip to content

Instantly share code, notes, and snippets.

@0x4f53
Last active January 1, 2022 17:12
Show Gist options
  • Save 0x4f53/ea53ea1e9b5fef93301c898d9b7b379a to your computer and use it in GitHub Desktop.
Save 0x4f53/ea53ea1e9b5fef93301c898d9b7b379a to your computer and use it in GitHub Desktop.
Python script to compare Java LazySodium signed messages with PyNaCl signed messages using Keypairs from both locations. Designed for Keyspace.
"""
(c) 2021 Keyspace
Compares a LazySodium / LibSodium output from Keyspace Android with PyNaCl's output and checks if LazySodium's signatures match PyNaCl's signature.
Note: LazySodium output on Android and PyNaCl output from this script must be the same due to determinism.
______________________________________________________________________________________________________________________________________________________
.-------> Public key -----> Verify data
Seed ----> Generate keypair ----| |_______________________________.
| on Android | |
| | |
| '-------> Secret key -----> Signed data |
| | |
| | |
| | |
| V |
| .-----> Verify key ---> [[ Verify data ]] <---'
'----> Generate keypair ------| ^
in Python | |
| |
| |
'----> Signing key -----> Signed data
"""
# All of the below values come from Keyspace Android's logcat
hardenedSeed = "6123cc8cb10eb9a23585183428b2d692c58407a05e558362c00811b05ee21eb2" # hardened seed from argon2i(bip39[seed]) output, to be used as input to keypair generation function
lazySodiumPrivateKey = "6123CC8CB10EB9A23585183428B2D692C58407A05E558362C00811B05EE21EB2629E8A3DFF89FD68A2D6721F0F41E032624140A8D13EE865A3E4AA46E3406D3C"
lazySodiumPublicKey = "629E8A3DFF89FD68A2D6721F0F41E032624140A8D13EE865A3E4AA46E3406D3C"
data = """{"expiry":1641032384056,"token":"e12de5ab-0891-45d1-adb0-a65f50491100","signedToken":"f0b5308927b9611574c3e865203719b85eee98aa243c00829f8b6f14e1731d5a"}""" # the actual message to sign.
androidSignedData = "4F46A31E6576F801D5670B9C741384EFC16E990721DF1051FC17AF91EE9CA7A1BFFCE6F8FB089593A84E8DC82AEBA26795242779686F6075F11BF09FC971E7037B22657870697279223A313634313033323338343035362C22746F6B656E223A2265313264653561622D303839312D343564312D616462302D613635663530343931313030222C227369676E6564546F6B656E223A2266306235333038393237623936313135373463336538363532303337313962383565656539386161323433633030383239663862366631346531373331643561227D"
import json
from nacl import encoding
from nacl.encoding import HexEncoder, Base64Encoder
from nacl.signing import SigningKey, VerifyKey, SignedMessage
from nacl.public import PrivateKey, PublicKey
androidSignedDataAsBytes = HexEncoder.decode((bytes(androidSignedData, encoding='utf-8')).decode())
androidSignature = androidSignedData[0:128]
androidSignatureAsBytes = HexEncoder.decode((bytes(androidSignature, encoding='utf-8')).decode())
dataAsBytes = bytes(data, encoding='utf-8')
print ("Data:\n" + data)
print ("Seed:\n" + hardenedSeed + "\n")
# On LazySodium Android, the private key from the seed was:
print ("Android LazySodium Private Key:\n" + lazySodiumPrivateKey)
print ("Android LazySodium Public Key:\n" + lazySodiumPublicKey)
private_key = SigningKey(bytes(hardenedSeed, encoding='utf-8'), encoder=HexEncoder) # Generate a signing/secret/private key from the Android generated hardened seed
signed = private_key.sign(bytes(data, encoding='utf-8')) # Sign message with the signing/secret/private key in PyNaCl
public_key = VerifyKey(bytes(lazySodiumPublicKey, encoding='utf-8'), encoder=HexEncoder) # Generate a verification/public key from the Android public key hexString
verificationStatus = public_key.verify(dataAsBytes, androidSignatureAsBytes)
print ("\nPyNaCl message from signature: \n" + str (signed.message) )
print ("Android pasted message from logcat:\n" + str (dataAsBytes) )
if signed.message == dataAsBytes: print ("\n[ ✓ ] Bytes match")
else: print ("\n[ x ] Error: Bytes do not match")
print ("\nPyNaCl produced signature: \n" + str (signed.signature) )
print ("Android pasted signature from logcat:\n" + str (androidSignatureAsBytes) )
if signed == androidSignedDataAsBytes: print ("\n[ ✓ ] Bytes match")
else: print ("\n[ x ] Error: Bytes do not match")
print("\n**********************************************************************\n")
if verificationStatus:
print ("[ ✓ ] MESSAGE VERIFIED! :)")
else:
print ("[ x ] INVALID / FORGED SIGNATURE! :(")
@0x4f53
Copy link
Author

0x4f53 commented Dec 30, 2021

Screenshot

Screenshot_20220101_223447


Edit: Error fixed. Turns out, I was using sealed boxes on instead of signing in LazySodium Android. :)

Changes

Before

cryptoBoxSeedPair()
cryptoSign()
cryptoSignVerify()

After

cryptoSignSeedPair()
cryptoSign() cryptoSignDetached() (changed to combined mode)
cryptoSignVerifyDetached() (detached verification function works for both combined mode and detached mode signatures)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment