0
votes

I'm trying to sign a message in Android, then send the message, signature and public key to a Django server for verification. I'm using pycryptodome on the server to verify, but verification always fails - problem is I have no idea which bit I'm doing wrong.

This is what I'm doing on Android:

Generate an RSA public private key

val keyPairGenerator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore")

val certStart = Calendar.getInstance()
val certEnd = Calendar.getInstance()
certEnd.add(Calendar.YEAR, 30)

val spec = KeyPairGeneratorSpec.Builder(context)
     .setAlias("MyKeyAlias")
     .setKeySize(1024)
     .setSubject(X500Principal("CN=CryptoIsHard"))
     .setSerialNumber(BigInteger.ONE)
     .setStartDate(certStart.time)
     .setEndDate(certEnd.time)
     .build()

 keyPairGenerator.initialize(spec, SecureRandom.getInstance("SHA1PRNG"))
 keyPairGenerator.generateKeyPair()

Sign a message using the private key, encode signature as base64

val message = "some message"
val keystoreEntry = keyStore.getEntry("MyKeyAlias", null) as KeyStore.PrivateKeyEntry
val signer= Signature.getInstance("SHA256withRSA")
signer.initSign(keystoreEntry.privateKey)
signer.update(message.toByteArray(Charsets.UTF_8))
val signature = Base64.encodeToString(signature.sign(), 0)

Extract the public key as base64

val keystoreEntry = keyStore.getEntry(KEY_ALIAS, null) as KeyStore.PrivateKeyEntry
val publicKey = Base64.encodeToString(keystoreEntry.certificate.publicKey.encoded, 0)

Send the message, signature (base64) and public key (base64) to server

Try to verify the signature

Then on the server (using Python 3, Django 1.11, pycryptodome 3.8.1), I receive the message, signed message, and public key:

from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256

def verify_signature(request):

    response_dict = json.loads(str(request.body, encoding='utf-8'))
    public_key = str(response_dict ['publicKey'])
    signed_message = str(response_dict ['signedMessage'])
    message = str(response_dict ['message'])

At this point I have something like this:

print(public_key)

MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCkmWj9+FLqpCRl35Ac8mFqfJx390kgvhQzNXPp
8/OWqsLitbCQYtw3/sEY60Cz04A3onZNh8iwSms8iZLSZh9Y23/AhSyBXw7XVtCwXQfuZagMwPV2
OhZZGGC8IOsqUOnRswa9L2/SI1i5HQQNqcZ+Su1Po/Xr2+opKps+gHDmPwIDAQAB

print(signed_message)

mdlasq+sN2Hdi+qBTWp6EjMBGYQCCdTSuQlrPozoA3J6dwW0cTXbp7YefD2JlLp8pXkMfYmTivsN
dAKkY/dHEAfUm4YuvaBz72ogwFx8px20JQ0OKVO03FjcZuw1jAGYUrEt8eswTHfmN8yJ/lDVsUlb
UYe/VcD6O/0YWrXGPrI=

Then I try to verify like so:

formatted_public_key = "-----BEGIN RSA KEY-----\n{}-----END RSA KEY-----".format(public_key)
rsa_public_key = RSA.importKey(formatted_public_key)
signature = PKCS1_v1_5.new(rsa_public_key)
digest = SHA256.new()
digest.update(message.encode())

verified = signature.verify(digest, signed_message.encode())

This always fails, and I can't figure out why. Some questions:

  • I've surrounded the public key with "-----BEGIN RSA KEY-----" and "-----END RSA KEY-----" - is that right? Without doing that I got an error about the RSA key format being invalid

  • My private key and signed message both have line breaks in them - should I remove these? I've tried with and without them, it doesn't seem to make a difference but would appreciate any advice

  • PyCharm complains about signature.verify(digest, signature.encode()):

    Expected type ModuleType, got SHA256Hash instead.

The docs say digest should be a type from Crypto.Hash, and I'm using Crypto.Hash.SHA256Hash, so why is PyCharm complaining? Can I ignore?

  • I'm using pycryptodome's PKCS1_v1_5 class to create a signature verifier. I have no idea what that is, I'm just following code I've seen on SO. Is it the right thing to use?

  • I'm using the algorithm "SHA256withRSA" to sign on android, and on the server using PKCS1_v1_5 with an RSA key and passing in a SHA256 hash of the message. I have very little understanding of all these standards. Does this sound right?

I have no idea where to go form here, any suggestions appreciated!

1

1 Answers

1
votes

I figured it out - I had forgotten that the signature had been encoded to base64 on the Android side, so needs to be decoded on the server side. So changing this:

signature.verify(digest, signed_message.encode())

to:

signature.verify(digest, base64.b64decode(signed_message))

Resolves the problem :)