Signature Verification Between Java and Python
Using public/private key pairs to digitally sign text is a common way to validate the authenticity of a piece of data. However dealing with RSA keys, certificates and signatures can often be a bit overwhelming, especially between two different languages or platforms. This tutorial will demonstrate how to use RSA keys to generate and validation signatures in both Java and Python 2.
For this demonstration, we’ll use two additional libraries. For Java, we’ll use Legion of the Bouncy Castle (seriously) and for Python we’ll use M2Crypto. I will not cover using pycrypto. I spent hours looking at many tutorials as well as pycrypto’s documentation and I could never get it to correctly generate or sign a digest in a way that could be verified by any other cryptology library. If anyone has working pycrypto examples, please either e-mail me or post them in the comments and I’ll update this tutorial accordingly.
Although it is possible to do RSA signature verification in the stock Java 1.6 environment, using the Bouncy Castle Provider (bcprov) allows for importing keys in the PEM files, a base64 encoded text files for X509 certificates with plain text anchor lines indicating the key types. Without bcprov, a key tool would be needed to convert the key(s) in the PEM file into the DER format that’s supported natively in Java.
First, we’ll look at signing a piece of data using Python. The following example takes two command line arguments, one is the key file and the second is the data to sign. The data file must exist, but if the key file does not, it will simply generate a new RSA private key and save it to the file in PEM format.
#!/usr/bin/env python2 from M2Crypto import EVP, RSA, X509 import sys import base64 from os import path # Sumit Khanna - PenguinDreams.org # Free for educational and non-commercial use if __name__ == '__main__': if len(sys.argv) != 3: sys.stderr.write('Usage ./pysign.py <pem file> <data file to sign>\n') sys.exit(1) pemFile = sys.argv[1] dataFile = sys.argv[2] if not path.isfile(dataFile): sys.stderr.write('Data file does not exist\n') if not path.isfile(pemFile): sys.stderr.write('PEM file does not exist. Generating\n') #keysize in bits is 2048, RSA public exponent is 65537 # Callback supresses ....++ output on key generation key = RSA.gen_key(2048, 65537,callback=lambda x, y, z:None) #Using a cipher of None prevents being prompted for a passphrase # A callback function can also be supplied key.save_pem(pemFile, cipher=None) key = EVP.load_key(pemFile) key.reset_context(md='sha1') key.sign_init() key.sign_update(open(dataFile,'r').read()) #Signatures are binary, so we base64 encode the result for portability print(base64.b64encode(key.sign_final()))
Next, we’ll create a Java class to verify the signature. It takes in the same arguments for a key file and a data file, however in this case the key must exist. The signature can be read from a file using the third argument, or taken from standard in. This allows us to chain the two programs together as we’ll see later.
/* * Sumit Khanna - PenguinDreams.org * Free for educational and non-commercial use */ import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.InputStreamReader; import java.io.PrintStream; import java.security.KeyPair; import java.security.PublicKey; import java.security.Security; import java.security.Signature; import javax.xml.bind.DatatypeConverter; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMReader; public class JavaVerify { public static PrintStream out = System.out; public static PrintStream err = System.err; public static void main(String[] args) throws Exception { if(args.length < 2 || args.length > 3) { err.println("Usage: java JavaVerify <pem file> <data file to verify> [signature file]"); err.println("\tIf no signature file is given, signature is taken via stdin"); System.exit(1); } File pemFile = new File(args[0]); File dataFile = new File(args[1]); if(!dataFile.exists()) { err.println("Data File Does Not Exist"); System.exit(1); } if(!pemFile.exists()) { err.println("PEM File Does Not Exist"); System.exit(1); } //BC Provider initalization Security.addProvider(new BouncyCastleProvider()); PEMReader pemReader = new PEMReader(new FileReader(pemFile)); PublicKey pubKey = ((KeyPair) pemReader.readObject()).getPublic(); //load public key Signature sg = Signature.getInstance("SHA1withRSA"); sg.initVerify(pubKey); //read data file into signature instance FileInputStream fin = new FileInputStream(dataFile); byte[] data = new byte[(int) dataFile.length()]; fin.read(data); sg.update(data); //read signature from file byte[] signature = new byte[0]; if(args.length == 3) { File sigFile = new File(args[2]); if(!sigFile.exists()) { err.println("Signature file could not be found"); System.exit(1); } fin = new FileInputStream(sigFile); signature = new byte[(int) sigFile.length()]; fin.read(signature); } //read signature from standard in else { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); signature = in.readLine().getBytes(); } //validate signature if(sg.verify(DatatypeConverter.parseBase64Binary(new String(signature)))) { out.println("Signature Verified"); System.exit(0); } else { out.println("Signature Verification Failed"); System.exit(2); } } }
To test these two programs, I’ve created a file called sample_data.xml
that contains some very basic XML data:
<?xml version="1.0" ?><SomeRandom><XML xml="data"/></SomeRandom>
Now, we can simply chain these two programs together to sign and verify data. Please note that for this to work, M2Crypto must already be present in your Python 2 installation. If you’re running Linux, most distributions have M2Crypto in their package manager, or you may install M2Crypto manually. The Bouncy Castle Provider must also be in the classpath
as shown in the following example:
export CLASSPATH=".:bcprov-jdk15-140.jar" javac JavaVerify.java ./pysign.py key.pem sample_data.xml | java JavaVerify key.pem sample_data.xml
We will get the result Signature Verified
after running these commands. You can try generating a second signature and using the two different signatures between the pysign
and JavaVerify
programs as well.
./pysign.py key1.pem sample_data.xml ./pysign.py key2.pem sample_data.xml| java JavaVerify key1.pem sample_data.xml
This will result in a Signature Verification Failed
.
It is also possible to generate the signature in Java with the following code.
/* * Sumit Khanna - PenguinDreams.org * Free for educational and non-commercial use */ import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.PrintStream; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.SecureRandom; import java.security.Security; import java.security.Signature; import javax.xml.bind.DatatypeConverter; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.JDKKeyPairGenerator; import org.bouncycastle.openssl.PEMReader; import org.bouncycastle.openssl.PEMWriter; class JavaSign { public static PrintStream out = System.out; public static PrintStream err = System.err; public static void main(String[] args) throws Exception { if(args.length != 2) { err.println("Usage: java JavaSign <pem file> <data file to sign>"); System.exit(1); } File pemFile = new File(args[0]); File dataFile = new File(args[1]); if(!dataFile.exists()) { err.println("Data File Does Not Exist"); System.exit(1); } Security.addProvider(new BouncyCastleProvider()); KeyPair keys = null; if(!pemFile.exists()) { err.println("PEM File Does Not Exist. Generating."); KeyPairGenerator r = KeyPairGenerator.getInstance("RSA"); //keysize in bits is 2048 r.initialize(2048,new SecureRandom()); keys = r.generateKeyPair(); PEMWriter pemWriter = new PEMWriter(new FileWriter(pemFile)); pemWriter.writeObject(keys); pemWriter.close(); //You must flush or close the file or else it will not save } else { keys = (KeyPair) new PEMReader(new FileReader(pemFile)).readObject(); } //read data file into signature instance FileInputStream fin = new FileInputStream(dataFile); byte[] data = new byte[(int) dataFile.length()]; fin.read(data); //Sign the data Signature sg = Signature.getInstance("SHA1withRSA"); sg.initSign(keys.getPrivate()); sg.update(data); //output base64 encoded binary signature out.println(DatatypeConverter.printBase64Binary(sg.sign())); } }
The following Python code can then be used to verify the signature:
#!/usr/bin/env python2 # Sumit Khanna - PenguinDreams.org # Free for educational and non-commercial use from M2Crypto import EVP, RSA, X509 import sys import base64 from os import path import fileinput if __name__ == '__main__': if len(sys.argv) < 3 and len(sys.argv) > 4: sys.stderr.write('Usage ./pyverify.py <pem file> <data file to verify> [signature file]\n') sys.stderr.write('\tIf no signature file is given, signature is taken via stdin\n') sys.exit(1) pemFile = sys.argv[1] dataFile = sys.argv[2] sigFile = sys.argv[3] if len(sys.argv) == 4 else '-' key = EVP.load_key(pemFile) if not path.isfile(pemFile): sys.stderr.write('PEM File could not be found\n') sys.exit(2) if not path.isfile(dataFile): sys.stderr.write('Data File could not be found\n') for line in fileinput.input(sigFile): key.reset_context(md='sha1') key.verify_init() key.verify_update(open(dataFile,'r').read()) if key.verify_final(base64.b64decode(line)): print "Signature Verified" sys.exit(0) else: print "Signature Verification Failed" sys.exit(2)
These programs can all be chained together to sign data and verify signatures
export CLASSPATH=".:bcprov-jdk15-140.jar" javac JavaSign.java JavaVerify.java ./pysign.py key1.pem sample_data.xml| java JavaVerify key1.pem sample_data.xml java JavaSign key1.pem sample_data.xml| ./pyverify.py key1.pem sample_data.xml ./pysign.py key1.pem sample_data.xml| ./pyverify.py key1.pem sample_data.xml java JavaSign key1.pem sample_data.xml| java JavaVerify key1.pem sample_data.xml
There are some important things to note in these examples. For one, we’re using a shared private key. The public key can always be generated from the private key, but the reverse cannot be done. Typically in a production environment, only the public key is accessible to the service that is responsible for verification and the private key is saved to the application which generates and signs the data. It is possible to save the public and private keys separately using the Bouncy Castle and M2Crypto API.
Another thing to note is that, at the time this writing, M2Crypto only supports Python 2. If your project is built on Python 3, you’ll have to find another cryptology library.
The source code for all of the mentioned examples is available for download:
Comments
tks,really helpful!