Difference between revisions of "Digital Signature Implementation in Java"

From OWASP
Jump to: navigation, search
 
m (minor / editorial (missing space))
 
(36 intermediate revisions by 6 users not shown)
Line 1: Line 1:
== Overview ==
+
== Status  ==
This article would give a brief overview of the concepts involved with Digital Signature and provide code sample for implementing Digital Signature in Java using the Java Cryptography Architecture
+
  
=== What is a Digital Signature ? ===
+
Released 14/1/2007
Digital Signature is a construct which helps achieve non-repudiation of Origin (ie. Origin Integrity) of data. By digitally signing the document, the person who signs it assures that he is the author of the document or the message that was signed.
+
  
=== Need for Digital Signature ===
+
[http://webhostinggeeks.com/science/digital-signature-ro Romanian Translation]
During the "E" revolution, there was a need for authenticating critical transactions especially in the financial World. If Alice has agreed to transfer $x to Bob, then there had to be a way for Bob to be sure that
+
        1. It was Alice who agreed for the transaction and not someone else impersonating Alice (Authentication)
+
        2. The amount agreed by Alice is $x (Integrity)
+
        3. Alice could not dispute her statement of transacting $x to Bob (Non-Repudiation of Origin)
+
These concerns were addressed with a solution known as Digital Signature.
+
To know more about Digital Signatures you can read Digital Signature article on Wikipedia.
+
  
== Digital Signatures in Java using JCA ==
+
== WARNING  ==
The Java Cryptography Architecture is a framework for accessing and developing cryptographic functionality for the Java platform. A JCA provider implements the cryptographic functionalities like Digital Signature and Message Digest. The default JCA provider in JDK 1.4.2 is SUN
+
 
 +
This wiki page was recently brought to my attention by Jim Manico, who asked me to review it for accuracy. In doing so, I noticed several errors and areas of weakness. Ultimately, I plan to revise this page (hopefully with the assistance of one of the original owners), but I do not have time to make a complete revision at the present time. Therefore, I will summarize what I see is problematic with the rest of this page and you can decide to use or not, with these caveats.<br>
 +
 
 +
#First and foremost, this page does ''not ''describe "digital signatures". Rather, it decribes a concept known as "digital envelopes", which is a scheme used with things like S/MIME. Digital signatures alone never encrypt the actual message text, but only encrypt a ''hash ''of the message text.
 +
#UTF-8 encoding should be used throughout to convert between Java Strings and byte arrays to ensure proper portability across different operating systems.
 +
#The certificate chain should always be validated. In the example here, the certificate is self-signed, so this is not relevant, but that will not be true in the normal case. Furthermore, it should be noted that self-signed certificates, while acceptable for demonstration purposes is considered a dubious practice for production as it opens the door for impersonation attacks.
 +
#NIST now recommends the use of 2048 bit key size for RSA or DSA keys.
 +
#There is a consistent use of weak algorithms. Here are some suggested replacements:
 +
 
 +
*Use "RSA/ECB/OAEPWithSHA1AndMGF1Padding" instead of "RSA/ECB/PKCS1Padding".
 +
*Use SHA1 (or better SHA256, but at least SHA1) instead of MD5 for the message digest.
 +
*Use "SHA1withRSA" for the signature algorithm rather than "MD5withRSA".
 +
*When creating a symmetric cipher to encrypt the plaintext message, use "AES/CBC/PKCS5Padding" and choose a random IV for each plaintext message rather than using simply "AES", which ends up using "AES/ECB/PKCS5Padding". ECB&nbsp;mode is extremely weak for regular plaintext. (It is OK for encrypting random bits though, which is why it is OK to use with RSA.) However, using CBC and PKCS5Padding could make you vulnerable to "padding oracle" attacks, so be careful. You can use ESAPI 2.0's Encryptor to avoid it. (Note also, this is part of the "digital envelope" concept. If this page were truly limited to "digital ''signatures''", it would not apply, as it would be irrelevant.)
 +
 
 +
I hope to get some time soon to clean up this wiki page. In the meantime, email me if you have questions.
 +
 
 +
-kevin wall
 +
 
 +
== Overview  ==
 +
 
 +
This article gives a brief overview of the concepts involved with Digital Signatures and provide code samples for implementing Digital Signatures in Java using the Java Cryptography Architecture.
 +
 
 +
=== What is a Digital Signature&nbsp;?  ===
 +
 
 +
A Digital Signature is a construct which helps achieve non-repudiation of Origin (ie. Origin Integrity) of data. By digitally signing the document, the person who signs it assures that he is the author of the document or the message that was signed.
 +
 
 +
=== Need for Digital Signature  ===
 +
 
 +
During the "E" revolution, there was a need for authenticating critical transactions especially in the financial World. If Alice has agreed to transfer $x to Bob, then there had to be a way for Bob to be sure that:
 +
 
 +
#It was Alice who performed the transaction and not someone else impersonating Alice (Authentication)
 +
#The amount agreed by Alice is $x (Integrity)
 +
#Alice could not dispute her statement of transacting $x to Bob (Non-Repudiation of Origin)
 +
 
 +
These concerns were addressed with a solution known as Digital Signatures. More background information about Digital Signatures can be found on the [http://en.wikipedia.org/wiki/Digital_signature Wikipedia article]
 +
 
 +
== Digital Signatures in Java using JCA ==
 +
 
 +
The Java Cryptography Architecture is a framework for accessing and developing cryptographic functionality for the Java platform. A JCA provider implements the cryptographic functionalities like Digital Signatures and Message Digests. The default JCA provider in JDK 1.4.2 is SUN.
 +
 
 +
=== Security Considerations while Implementing Digital Signature  ===
 +
 
 +
Two main Security considerations should be taken into account when implementing Digital Signatures.
 +
 
 +
#Sign the message and then encrypt the signed message
 +
#Sign the Hash of the message instead of the entire message
 +
 
 +
=== Performance Considerations while Implementing Digital Signature  ===
 +
 
 +
Since Asymmetric encryption algorithms like RSA, DSA are computationally slower than symmetric encryption algorithms like AES, it is good practice to encrypt the actual message to be transmitted using a Symmetric key Algorithm and then encrypt the key used in the Symmetric Key Algorithm using an Asymmetric Key Algorithm. E.g.: if one wants to transmit the message "Hello World of Digital Signatures", then first encrypt this message using a symmetric key ,say an 128 bit AES key like x7oFaHSPnWxEMiZE/0qYrg and then encrypt this key using an asymmetric key algorithm like RSA.
 +
 
 +
== Algorithm for Implementing Digital signature using RSA Algorithm  ==
 +
 
 +
The RSA implementation provider in Java has a limitation in that encryption can be done only on data of length &lt;= 117 bytes. If the data is of length &gt; 117 bytes, it would throw an IllegalBlockSizeException&nbsp;: Data must not be longer than 117 bytes Hence the symmetric has to be encrypted and then signed.
 +
 
 +
The RSA algorithm with PKCS#1 padding can only encrypt data of size k - 11 [http://www.rsa.com/rsalabs/node.asp?id=2125 1], where k is the octet length of the RSA modulus and 11 is amount of bytes used by PCKS#1 v1.5 padding. Hence if we use an RSA key of size 1024 bits, we could encrypt only 128 - 11 =&gt; 117 bytes of data. There are 2 options available to encrypt data of larger byte size.
 +
 
 +
#We could use an RSA key of length &gt; 1024. For eg, say if we use 2048 bits, then we could encrypt 256 - 11 =&gt; 245 bytes of data. The disadvantage with this approach is that we would not be able to encrypt data of size &gt; x bytes (in the above example x is 245).
 +
#Break down the input data into chunks of bytes of size &lt; 117 and apply encryption on each chunk. Code sample for this approach can be found [http://www.aviransplace.com/2004/10/12/using-rsa-encryption-with-java/3/ here]
 +
 
 +
Both these approaches would have performance implications since RSA key of larger sizes or a "divide and conquer" approach on input bytes are computationally expensive.
 +
 
 +
=== Algorithm  ===
 +
 
 +
With the above considerations, the algorithm below can be used for implementing public key cryptography in Java.
 +
 
 +
#Encrypt the message using a symmetric key.
 +
#Concatenate the symmetric key + Hash of symmetric key + Hash of message.
 +
#Encrypt the concatenated string using the receivers public key.
 +
#Sign the data to be transmitted (Encrypted symmetric key + Hash of the key + Hash of message).
 +
#Validate the Signature.
 +
#Decrypt the message using Receiver private key to get the symmetric key.
 +
#Validate the integrity of the key using the Hash of the key.
 +
#Decrypt the actual message using the symmetric key which has been decrypted and parsed and checked for integrity.
 +
#Compute MessageDigest of data.
 +
#Validate if the Message Digest of the decrypted text matches the Message Digest of the Original Message.
 +
 
 +
=== Commands for generating keys  ===
 +
 
 +
prompt# keytool -genkey -alias testsender -keystore testkeystore.ks -keyalg RSA Enter keystore password: testpwd What is your first and last name?
 +
 
 +
[Unknown]:  Alice Sender
 +
 
 +
What is the name of your organizational unit?
 +
 
 +
[Unknown]:  IT
 +
 
 +
What is the name of your organization?
 +
 
 +
[Unknown]:  ABC Inc
 +
 
 +
What is the name of your City or Locality?
 +
 
 +
[Unknown]:  LA
 +
 
 +
What is the name of your State or Province?
 +
 
 +
[Unknown]:  CA
 +
 
 +
What is the two-letter country code for this unit?
 +
 
 +
[Unknown]:  US
 +
 
 +
Is CN=Alice Sender, OU=IT, O=ABC Inc, L=LA, ST=CA, C=US correct?
 +
 
 +
[no]:  y
 +
 
 +
Enter key password for &lt;testsender&gt;
 +
 
 +
(RETURN if same as keystore password):  send123
 +
 
 +
prompt # keytool -genkey -alias testrecv -keystore testkeystore.ks -keyalg RSA Enter keystore password: testpwd What is your first and last name?
 +
 
 +
[Unknown]:  Bob Receiver
 +
 
 +
What is the name of your organizational unit?
 +
 
 +
[Unknown]:  HR
 +
 
 +
What is the name of your organization?
 +
 
 +
[Unknown]:  ABC Inc
 +
 
 +
What is the name of your City or Locality?
 +
 
 +
[Unknown]:  SFO
 +
 
 +
What is the name of your State or Province?
 +
 
 +
[Unknown]:  CA
 +
 
 +
What is the two-letter country code for this unit?
 +
 
 +
[Unknown]:  US
 +
 
 +
Is CN=Bob Receiver, OU=HR, O=ABC Inc, L=SFO, ST=CA, C=US correct?
 +
 
 +
[no]:  y
 +
 
 +
Enter key password for &lt;testrecv&gt;
 +
 
 +
(RETURN if same as keystore password):  recv123
 +
 
 +
=== Code Sample  ===
 +
 
 +
==== PublicKeyCryptography.java  ====
 +
<pre>package org.owasp.crypto;
 +
 
 +
import java.security.*;
 +
import java.security.cert.*;
 +
import javax.crypto.*;
 +
import sun.misc.BASE64Encoder;
 +
import sun.misc.BASE64Decoder;
 +
 
 +
 
 +
/**
 +
*
 +
* @author Joe Prasanna Kumar
 +
*
 +
* 1. Encrypt the data using a Symmetric Key
 +
* 2. Encrypt the Symmetric key using the Receivers public key
 +
* 3. Create a Message Digest of the data to be transmitted
 +
* 4. Sign the message to be transmitted
 +
* 5. Send the data over to an unsecured channel
 +
* 6. Validate the Signature
 +
* 7. Decrypt the message using Recv private Key to get the Symmetric Key
 +
* 8. Decrypt the data using the Symmetric Key
 +
* 9. Compute MessageDigest of data + Signed message
 +
* 10.Validate if the Message Digest of the Decrypted Text matches the Message Digest of the Original Message
 +
*
 +
*
 +
*/
 +
 
 +
public class PublicKeyCryptography {
 +
 
 +
/**
 +
* @param args
 +
*/
 +
public static void main(String[] args) {
 +
 +
SymmetricEncrypt encryptUtil = new SymmetricEncrypt();
 +
String strDataToEncrypt = "Hello World";
 +
byte[] byteDataToTransmit = strDataToEncrypt.getBytes();
 +
 
 +
// Generating a SecretKey for Symmetric Encryption
 +
SecretKey senderSecretKey = SymmetricEncrypt.getSecret();
 +
 +
//1. Encrypt the data using a Symmetric Key
 +
byte[] byteCipherText = encryptUtil.encryptData(byteDataToTransmit,senderSecretKey,"AES");
 +
String strCipherText = new BASE64Encoder().encode(byteCipherText);
 +
 +
 +
//2. Encrypt the Symmetric key using the Receivers public key
 +
try{
 +
// 2.1 Specify the Keystore where the Receivers certificate has been imported
 +
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
 +
char [] password = "testpwd".toCharArray();
 +
java.io.FileInputStream fis = new java.io.FileInputStream("/home/Joebi/workspace/OWASP_Crypto/org/owasp/crypto/testkeystore.ks");
 +
    ks.load(fis, password);
 +
    fis.close();
 +
   
 +
// 2.2 Creating an X509 Certificate of the Receiver
 +
    X509Certificate recvcert&nbsp;;
 +
    MessageDigest md = MessageDigest.getInstance("MD5");
 +
    recvcert = (X509Certificate)ks.getCertificate("testrecv");
 +
    // 2.3 Getting the Receivers public Key from the Certificate
 +
    PublicKey pubKeyReceiver = recvcert.getPublicKey();
 +
   
 +
    // 2.4 Encrypting the SecretKey with the Receivers public Key
 +
    byte[] byteEncryptWithPublicKey = encryptUtil.encryptData(senderSecretKey.getEncoded(),pubKeyReceiver,"RSA/ECB/PKCS1Padding");
 +
    String strSenbyteEncryptWithPublicKey = new BASE64Encoder().encode(byteEncryptWithPublicKey);
 +
       
 +
    // 3. Create a Message Digest of the Data to be transmitted
 +
    md.update(byteDataToTransmit);
 +
byte byteMDofDataToTransmit[] = md.digest();
 +
 +
String strMDofDataToTransmit = new String();
 +
for (int i = 0; i &lt; byteMDofDataToTransmit.length; i++){
 +
strMDofDataToTransmit = strMDofDataToTransmit + Integer.toHexString((int)byteMDofDataToTransmit[i] &amp; 0xFF)&nbsp;;
 +
            }
 +
 +
    // 3.1 Message to be Signed = Encrypted Secret Key + MAC of the data to be transmitted
 +
String strMsgToSign = strSenbyteEncryptWithPublicKey + "|" + strMDofDataToTransmit;
 +
   
 +
    // 4. Sign the message
 +
    // 4.1 Get the private key of the Sender from the keystore by providing the password set for the private key while creating the keys using keytool
 +
char[] keypassword = "send123".toCharArray();
 +
    Key myKey =  ks.getKey("testsender", keypassword);
 +
    PrivateKey myPrivateKey = (PrivateKey)myKey;
 +
   
 +
    // 4.2 Sign the message
 +
    Signature mySign = Signature.getInstance("MD5withRSA");
 +
    mySign.initSign(myPrivateKey);
 +
    mySign.update(strMsgToSign.getBytes());
 +
    byte[] byteSignedData = mySign.sign();
 +
       
 +
// 5. The Values byteSignedData (the signature) and strMsgToSign (the data which was signed) can be sent across to the receiver
 +
 +
// 6.Validate the Signature
 +
    // 6.1 Extracting the Senders public Key from his certificate
 +
X509Certificate sendercert&nbsp;;
 +
sendercert = (X509Certificate)ks.getCertificate("testsender");
 +
    PublicKey pubKeySender = sendercert.getPublicKey();
 +
    // 6.2 Verifying the Signature
 +
    Signature myVerifySign = Signature.getInstance("MD5withRSA");
 +
    myVerifySign.initVerify(pubKeySender);
 +
    myVerifySign.update(strMsgToSign.getBytes());
 +
   
 +
    boolean verifySign = myVerifySign.verify(byteSignedData);
 +
    if (verifySign == false)
 +
    {
 +
    System.out.println(" Error in validating Signature ");
 +
    }
 +
   
 +
    else
 +
    System.out.println(" Successfully validated Signature ");
 +
 
 +
    // 7. Decrypt the message using Recv private Key to get the Symmetric Key
 +
    char[] recvpassword = "recv123".toCharArray();
 +
    Key recvKey =  ks.getKey("testrecv", recvpassword);
 +
    PrivateKey recvPrivateKey = (PrivateKey)recvKey;
 +
   
 +
    // Parsing the MessageDigest and the encrypted value
 +
    String strRecvSignedData = new String (byteSignedData);
 +
    String[] strRecvSignedDataArray = new String [10];
 +
    strRecvSignedDataArray = strMsgToSign.split("|");
 +
    int intindexofsep = strMsgToSign.indexOf("|");
 +
    String strEncryptWithPublicKey = strMsgToSign.substring(0,intindexofsep);
 +
    String strHashOfData = strMsgToSign.substring(intindexofsep+1);
 +
 
 +
    // Decrypting to get the symmetric key
 +
    byte[] bytestrEncryptWithPublicKey = new BASE64Decoder().decodeBuffer(strEncryptWithPublicKey);
 +
    byte[] byteDecryptWithPrivateKey = encryptUtil.decryptData(byteEncryptWithPublicKey,recvPrivateKey,"RSA/ECB/PKCS1Padding");
 +
   
 +
    // 8. Decrypt the data using the Symmetric Key
 +
    javax.crypto.spec.SecretKeySpec secretKeySpecDecrypted = new javax.crypto.spec.SecretKeySpec(byteDecryptWithPrivateKey,"AES");
 +
    byte[] byteDecryptText = encryptUtil.decryptData(byteCipherText,secretKeySpecDecrypted,"AES");
 +
    String strDecryptedText = new String(byteDecryptText);
 +
    System.out.println(" Decrypted data is " +strDecryptedText);
 +
   
 +
    // 9. Compute MessageDigest of data + Signed message
 +
    MessageDigest recvmd = MessageDigest.getInstance("MD5");
 +
    recvmd.update(byteDecryptText);
 +
byte byteHashOfRecvSignedData[] = recvmd.digest();
 +
 
 +
String strHashOfRecvSignedData = new String();
 +
 +
for (int i = 0; i &lt; byteHashOfRecvSignedData.length; i++){
 +
strHashOfRecvSignedData = strHashOfRecvSignedData + Integer.toHexString((int)byteHashOfRecvSignedData[i] &amp; 0xFF)&nbsp;;
 +
            }
 +
// 10. Validate if the Message Digest of the Decrypted Text matches the Message Digest of the Original Message
 +
if (!strHashOfRecvSignedData.equals(strHashOfData))
 +
{
 +
System.out.println(" Message has been tampered ");
 +
}
 +
 +
}
 +
 +
catch(Exception exp)
 +
{
 +
System.out.println(" Exception caught " + exp);
 +
exp.printStackTrace();
 +
}
 +
 +
 +
}
 +
 
 +
}
 +
</pre>
 +
==== SymmetricEncrypt.java  ====
 +
<pre>package org.owasp.crypto;
 +
 
 +
import javax.crypto.KeyGenerator;
 +
import javax.crypto.SecretKey;
 +
import javax.crypto.Cipher;
 +
import java.security.Key;
 +
 
 +
import java.security.NoSuchAlgorithmException;
 +
import java.security.InvalidKeyException;
 +
import java.security.InvalidAlgorithmParameterException;
 +
import javax.crypto.NoSuchPaddingException;
 +
import javax.crypto.BadPaddingException;
 +
import javax.crypto.IllegalBlockSizeException;
 +
 
 +
import sun.misc.BASE64Encoder;
 +
 
 +
/**
 +
* @author Joe Prasanna Kumar
 +
* This program provides the following cryptographic functionalities
 +
* 1. Encryption using AES
 +
* 2. Decryption using AES
 +
*
 +
* High Level Algorithm&nbsp;:
 +
* 1. Generate a DES key (specify the Key size during this phase)
 +
* 2. Create the Cipher
 +
* 3. To Encrypt&nbsp;: Initialize the Cipher for Encryption
 +
* 4. To Decrypt&nbsp;: Initialize the Cipher for Decryption
 +
*
 +
*
 +
*/
 +
 
 +
public class SymmetricEncrypt {
 +
 +
String strDataToEncrypt = new String();
 +
String strCipherText = new String();
 +
String strDecryptedText = new String();
 +
static KeyGenerator keyGen;
 +
private static String strHexVal = "0123456789abcdef";
 +
 
 +
public static SecretKey getSecret(){
 +
/**
 +
*  Step 1. Generate an AES key using KeyGenerator
 +
*  Initialize the keysize to 128
 +
*
 +
*/
 +
 +
try{
 +
keyGen = KeyGenerator.getInstance("AES");
 +
keyGen.init(128);
 +
 
 +
}
 +
 +
catch(Exception exp)
 +
{
 +
System.out.println(" Exception inside constructor " +exp);
 +
}
 +
 +
SecretKey secretKey = keyGen.generateKey();
 +
return secretKey;
 +
}
 +
 +
/**
 +
*  Step2. Create a Cipher by specifying the following parameters
 +
* a. Algorithm name - here it is AES
 +
*/
 +
 +
 +
public byte[] encryptData(byte[] byteDataToEncrypt, Key secretKey, String Algorithm) {
 +
byte[] byteCipherText = new byte[200];
 +
 +
try {
 +
Cipher aesCipher = Cipher.getInstance(Algorithm);
 +
 +
/**
 +
*  Step 3. Initialize the Cipher for Encryption
 +
*/
 +
if(Algorithm.equals("AES")){
 +
aesCipher.init(Cipher.ENCRYPT_MODE,secretKey,aesCipher.getParameters());
 +
}
 +
else if(Algorithm.equals("RSA/ECB/PKCS1Padding")){
 +
aesCipher.init(Cipher.ENCRYPT_MODE,secretKey);
 +
}
 +
 +
/**
 +
*  Step 4. Encrypt the Data
 +
*  1. Declare / Initialize the Data. Here the data is of type String
 +
*  2. Convert the Input Text to Bytes
 +
*  3. Encrypt the bytes using doFinal method
 +
*/
 +
byteCipherText = aesCipher.doFinal(byteDataToEncrypt);
 +
strCipherText = new BASE64Encoder().encode(byteCipherText);
 +
 
 +
}
 +
 +
catch (NoSuchAlgorithmException noSuchAlgo)
 +
{
 +
System.out.println(" No Such Algorithm exists " + noSuchAlgo);
 +
}
 +
 +
catch (NoSuchPaddingException noSuchPad)
 +
{
 +
System.out.println(" No Such Padding exists " + noSuchPad);
 +
}
 +
 +
catch (InvalidKeyException invalidKey)
 +
{
 +
System.out.println(" Invalid Key " + invalidKey);
 +
}
 +
 +
catch (BadPaddingException badPadding)
 +
{
 +
System.out.println(" Bad Padding " + badPadding);
 +
}
 +
 +
catch (IllegalBlockSizeException illegalBlockSize)
 +
{
 +
System.out.println(" Illegal Block Size " + illegalBlockSize);
 +
illegalBlockSize.printStackTrace();
 +
}
 +
catch (Exception exp)
 +
{
 +
exp.printStackTrace();
 +
}
 +
 +
return byteCipherText;
 +
}
 +
/**
 +
*  Step 5. Decrypt the Data
 +
*  1. Initialize the Cipher for Decryption
 +
*  2. Decrypt the cipher bytes using doFinal method
 +
*/
 +
 +
public byte[] decryptData(byte[] byteCipherText, Key secretKey, String Algorithm) {
 +
byte[] byteDecryptedText = new byte[200];
 +
 +
try{
 +
Cipher aesCipher = Cipher.getInstance(Algorithm);
 +
if(Algorithm.equals("AES")){
 +
aesCipher.init(Cipher.DECRYPT_MODE,secretKey,aesCipher.getParameters());
 +
}
 +
else if(Algorithm.equals("RSA/ECB/PKCS1Padding")){
 +
aesCipher.init(Cipher.DECRYPT_MODE,secretKey);
 +
}
 +
 +
byteDecryptedText = aesCipher.doFinal(byteCipherText);
 +
strDecryptedText = new String(byteDecryptedText);
 +
}
 +
 +
catch (NoSuchAlgorithmException noSuchAlgo)
 +
{
 +
System.out.println(" No Such Algorithm exists " + noSuchAlgo);
 +
}
 +
 +
catch (NoSuchPaddingException noSuchPad)
 +
{
 +
System.out.println(" No Such Padding exists " + noSuchPad);
 +
}
 +
 +
catch (InvalidKeyException invalidKey)
 +
{
 +
System.out.println(" Invalid Key " + invalidKey);
 +
invalidKey.printStackTrace();
 +
}
 +
 +
catch (BadPaddingException badPadding)
 +
{
 +
System.out.println(" Bad Padding " + badPadding);
 +
badPadding.printStackTrace();
 +
}
 +
 +
catch (IllegalBlockSizeException illegalBlockSize)
 +
{
 +
System.out.println(" Illegal Block Size " + illegalBlockSize);
 +
illegalBlockSize.printStackTrace();
 +
}
 +
 +
catch (InvalidAlgorithmParameterException invalidParam)
 +
{
 +
System.out.println(" Invalid Parameter " + invalidParam);
 +
}
 +
 +
return byteDecryptedText;
 +
}
 +
 +
 +
public static byte[] convertStringToByteArray(String strInput) {
 +
strInput = strInput.toLowerCase();
 +
byte[] byteConverted = new byte[(strInput.length() + 1) / 2];
 +
int j = 0;
 +
int interimVal;
 +
int nibble = -1;
 +
 
 +
for (int i = 0; i &lt; strInput.length(); ++i) {
 +
interimVal = strHexVal.indexOf(strInput.charAt(i));
 +
if (interimVal &gt;= 0) {
 +
if (nibble &lt; 0) {
 +
nibble = interimVal;
 +
} else {
 +
byteConverted[j++] = (byte) ((nibble &lt;&lt; 4) + interimVal);
 +
nibble = -1;
 +
}
 +
}
 +
}
 +
 
 +
if (nibble &gt;= 0) {
 +
byteConverted[j++] = (byte) (nibble &lt;&lt; 4);
 +
}
 +
 
 +
if (j &lt; byteConverted.length) {
 +
byte[] byteTemp = new byte[j];
 +
System.arraycopy(byteConverted, 0, byteTemp, 0, j);
 +
byteConverted = byteTemp;
 +
}
 +
 
 +
return byteConverted;
 +
}
 +
 +
public static String convertByteArrayToString(byte[] block) {
 +
StringBuffer buf = new StringBuffer();
 +
 
 +
for (int i = 0; i &lt; block.length; ++i) {
 +
buf.append(strHexVal.charAt((block[i] &gt;&gt;&gt; 4) &amp; 0xf));
 +
buf.append(strHexVal.charAt(block[i] &amp; 0xf));
 +
}
 +
 
 +
return buf.toString();
 +
}
 +
}
 +
</pre>
 +
== References  ==
 +
 
 +
#Computer Security Arts and Science – Matt Bishop
 +
#Core Security Patterns – Christopher Steele, Ray Lai and Ramesh Nagappan
 +
 
 +
[[Category:OWASP_Java_Project]]
 +
 
 +
 
 +
== '''Traducción Español'''==
 +
 
 +
Para mayor detalle vea: [[Traducción Español]]

Latest revision as of 09:43, 19 November 2013

Contents

Status

Released 14/1/2007

Romanian Translation

WARNING

This wiki page was recently brought to my attention by Jim Manico, who asked me to review it for accuracy. In doing so, I noticed several errors and areas of weakness. Ultimately, I plan to revise this page (hopefully with the assistance of one of the original owners), but I do not have time to make a complete revision at the present time. Therefore, I will summarize what I see is problematic with the rest of this page and you can decide to use or not, with these caveats.

  1. First and foremost, this page does not describe "digital signatures". Rather, it decribes a concept known as "digital envelopes", which is a scheme used with things like S/MIME. Digital signatures alone never encrypt the actual message text, but only encrypt a hash of the message text.
  2. UTF-8 encoding should be used throughout to convert between Java Strings and byte arrays to ensure proper portability across different operating systems.
  3. The certificate chain should always be validated. In the example here, the certificate is self-signed, so this is not relevant, but that will not be true in the normal case. Furthermore, it should be noted that self-signed certificates, while acceptable for demonstration purposes is considered a dubious practice for production as it opens the door for impersonation attacks.
  4. NIST now recommends the use of 2048 bit key size for RSA or DSA keys.
  5. There is a consistent use of weak algorithms. Here are some suggested replacements:
  • Use "RSA/ECB/OAEPWithSHA1AndMGF1Padding" instead of "RSA/ECB/PKCS1Padding".
  • Use SHA1 (or better SHA256, but at least SHA1) instead of MD5 for the message digest.
  • Use "SHA1withRSA" for the signature algorithm rather than "MD5withRSA".
  • When creating a symmetric cipher to encrypt the plaintext message, use "AES/CBC/PKCS5Padding" and choose a random IV for each plaintext message rather than using simply "AES", which ends up using "AES/ECB/PKCS5Padding". ECB mode is extremely weak for regular plaintext. (It is OK for encrypting random bits though, which is why it is OK to use with RSA.) However, using CBC and PKCS5Padding could make you vulnerable to "padding oracle" attacks, so be careful. You can use ESAPI 2.0's Encryptor to avoid it. (Note also, this is part of the "digital envelope" concept. If this page were truly limited to "digital signatures", it would not apply, as it would be irrelevant.)

I hope to get some time soon to clean up this wiki page. In the meantime, email me if you have questions.

-kevin wall

Overview

This article gives a brief overview of the concepts involved with Digital Signatures and provide code samples for implementing Digital Signatures in Java using the Java Cryptography Architecture.

What is a Digital Signature ?

A Digital Signature is a construct which helps achieve non-repudiation of Origin (ie. Origin Integrity) of data. By digitally signing the document, the person who signs it assures that he is the author of the document or the message that was signed.

Need for Digital Signature

During the "E" revolution, there was a need for authenticating critical transactions especially in the financial World. If Alice has agreed to transfer $x to Bob, then there had to be a way for Bob to be sure that:

  1. It was Alice who performed the transaction and not someone else impersonating Alice (Authentication)
  2. The amount agreed by Alice is $x (Integrity)
  3. Alice could not dispute her statement of transacting $x to Bob (Non-Repudiation of Origin)

These concerns were addressed with a solution known as Digital Signatures. More background information about Digital Signatures can be found on the Wikipedia article

Digital Signatures in Java using JCA

The Java Cryptography Architecture is a framework for accessing and developing cryptographic functionality for the Java platform. A JCA provider implements the cryptographic functionalities like Digital Signatures and Message Digests. The default JCA provider in JDK 1.4.2 is SUN.

Security Considerations while Implementing Digital Signature

Two main Security considerations should be taken into account when implementing Digital Signatures.

  1. Sign the message and then encrypt the signed message
  2. Sign the Hash of the message instead of the entire message

Performance Considerations while Implementing Digital Signature

Since Asymmetric encryption algorithms like RSA, DSA are computationally slower than symmetric encryption algorithms like AES, it is good practice to encrypt the actual message to be transmitted using a Symmetric key Algorithm and then encrypt the key used in the Symmetric Key Algorithm using an Asymmetric Key Algorithm. E.g.: if one wants to transmit the message "Hello World of Digital Signatures", then first encrypt this message using a symmetric key ,say an 128 bit AES key like x7oFaHSPnWxEMiZE/0qYrg and then encrypt this key using an asymmetric key algorithm like RSA.

Algorithm for Implementing Digital signature using RSA Algorithm

The RSA implementation provider in Java has a limitation in that encryption can be done only on data of length <= 117 bytes. If the data is of length > 117 bytes, it would throw an IllegalBlockSizeException : Data must not be longer than 117 bytes Hence the symmetric has to be encrypted and then signed.

The RSA algorithm with PKCS#1 padding can only encrypt data of size k - 11 1, where k is the octet length of the RSA modulus and 11 is amount of bytes used by PCKS#1 v1.5 padding. Hence if we use an RSA key of size 1024 bits, we could encrypt only 128 - 11 => 117 bytes of data. There are 2 options available to encrypt data of larger byte size.

  1. We could use an RSA key of length > 1024. For eg, say if we use 2048 bits, then we could encrypt 256 - 11 => 245 bytes of data. The disadvantage with this approach is that we would not be able to encrypt data of size > x bytes (in the above example x is 245).
  2. Break down the input data into chunks of bytes of size < 117 and apply encryption on each chunk. Code sample for this approach can be found here

Both these approaches would have performance implications since RSA key of larger sizes or a "divide and conquer" approach on input bytes are computationally expensive.

Algorithm

With the above considerations, the algorithm below can be used for implementing public key cryptography in Java.

  1. Encrypt the message using a symmetric key.
  2. Concatenate the symmetric key + Hash of symmetric key + Hash of message.
  3. Encrypt the concatenated string using the receivers public key.
  4. Sign the data to be transmitted (Encrypted symmetric key + Hash of the key + Hash of message).
  5. Validate the Signature.
  6. Decrypt the message using Receiver private key to get the symmetric key.
  7. Validate the integrity of the key using the Hash of the key.
  8. Decrypt the actual message using the symmetric key which has been decrypted and parsed and checked for integrity.
  9. Compute MessageDigest of data.
  10. Validate if the Message Digest of the decrypted text matches the Message Digest of the Original Message.

Commands for generating keys

prompt# keytool -genkey -alias testsender -keystore testkeystore.ks -keyalg RSA Enter keystore password: testpwd What is your first and last name?

[Unknown]:  Alice Sender

What is the name of your organizational unit?

[Unknown]:  IT

What is the name of your organization?

[Unknown]:  ABC Inc

What is the name of your City or Locality?

[Unknown]:  LA

What is the name of your State or Province?

[Unknown]:  CA

What is the two-letter country code for this unit?

[Unknown]:  US

Is CN=Alice Sender, OU=IT, O=ABC Inc, L=LA, ST=CA, C=US correct?

[no]:  y

Enter key password for <testsender>

(RETURN if same as keystore password):  send123

prompt # keytool -genkey -alias testrecv -keystore testkeystore.ks -keyalg RSA Enter keystore password: testpwd What is your first and last name?

[Unknown]:  Bob Receiver

What is the name of your organizational unit?

[Unknown]:  HR

What is the name of your organization?

[Unknown]:  ABC Inc

What is the name of your City or Locality?

[Unknown]:  SFO

What is the name of your State or Province?

[Unknown]:  CA

What is the two-letter country code for this unit?

[Unknown]:  US

Is CN=Bob Receiver, OU=HR, O=ABC Inc, L=SFO, ST=CA, C=US correct?

[no]:  y

Enter key password for <testrecv>

(RETURN if same as keystore password):  recv123

Code Sample

PublicKeyCryptography.java

package org.owasp.crypto;

import java.security.*;
import java.security.cert.*;
import javax.crypto.*;
import sun.misc.BASE64Encoder;
import sun.misc.BASE64Decoder;


/**
 * 
 * @author Joe Prasanna Kumar
 * 
 * 1. Encrypt the data using a Symmetric Key
 * 2. Encrypt the Symmetric key using the Receivers public key
 * 3. Create a Message Digest of the data to be transmitted
 * 4. Sign the message to be transmitted
 * 5. Send the data over to an unsecured channel
 * 6. Validate the Signature
 * 7. Decrypt the message using Recv private Key to get the Symmetric Key
 * 8. Decrypt the data using the Symmetric Key
 * 9. Compute MessageDigest of data + Signed message
 * 10.Validate if the Message Digest of the Decrypted Text matches the Message Digest of the Original Message
 * 
 * 
 */

public class PublicKeyCryptography {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
	SymmetricEncrypt encryptUtil = new SymmetricEncrypt();
	String strDataToEncrypt = "Hello World";
	byte[] byteDataToTransmit = strDataToEncrypt.getBytes();

	// Generating a SecretKey for Symmetric Encryption
	SecretKey senderSecretKey = SymmetricEncrypt.getSecret();
	
	//1. Encrypt the data using a Symmetric Key
	byte[] byteCipherText = encryptUtil.encryptData(byteDataToTransmit,senderSecretKey,"AES");
	String strCipherText = new BASE64Encoder().encode(byteCipherText);
	
	
	//2. Encrypt the Symmetric key using the Receivers public key
	try{
		// 2.1 Specify the Keystore where the Receivers certificate has been imported
	KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
	char [] password = "testpwd".toCharArray();
	java.io.FileInputStream fis = new java.io.FileInputStream("/home/Joebi/workspace/OWASP_Crypto/org/owasp/crypto/testkeystore.ks");
    ks.load(fis, password);
    fis.close();
    
	// 2.2 Creating an X509 Certificate of the Receiver
    X509Certificate recvcert ;
    MessageDigest md = MessageDigest.getInstance("MD5");
    recvcert = (X509Certificate)ks.getCertificate("testrecv");
    // 2.3 Getting the Receivers public Key from the Certificate
    PublicKey pubKeyReceiver = recvcert.getPublicKey();
    
    // 2.4 Encrypting the SecretKey with the Receivers public Key
    byte[] byteEncryptWithPublicKey = encryptUtil.encryptData(senderSecretKey.getEncoded(),pubKeyReceiver,"RSA/ECB/PKCS1Padding");
    String strSenbyteEncryptWithPublicKey = new BASE64Encoder().encode(byteEncryptWithPublicKey);
        
    // 3. Create a Message Digest of the Data to be transmitted
    md.update(byteDataToTransmit);
	byte byteMDofDataToTransmit[] = md.digest();
	
	String strMDofDataToTransmit = new String();
	for (int i = 0; i < byteMDofDataToTransmit.length; i++){
		strMDofDataToTransmit = strMDofDataToTransmit + Integer.toHexString((int)byteMDofDataToTransmit[i] & 0xFF) ;
             }
	
    // 3.1 Message to be Signed = Encrypted Secret Key + MAC of the data to be transmitted
	String strMsgToSign = strSenbyteEncryptWithPublicKey + "|" + strMDofDataToTransmit;
    
    // 4. Sign the message
    // 4.1 Get the private key of the Sender from the keystore by providing the password set for the private key while creating the keys using keytool
	char[] keypassword = "send123".toCharArray();
    Key myKey =  ks.getKey("testsender", keypassword);
    PrivateKey myPrivateKey = (PrivateKey)myKey;
    
    // 4.2 Sign the message
    Signature mySign = Signature.getInstance("MD5withRSA");
    mySign.initSign(myPrivateKey);
    mySign.update(strMsgToSign.getBytes());
    byte[] byteSignedData = mySign.sign();
        
	// 5. The Values byteSignedData (the signature) and strMsgToSign (the data which was signed) can be sent across to the receiver
	
	// 6.Validate the Signature
    // 6.1 Extracting the Senders public Key from his certificate
	X509Certificate sendercert ;
	sendercert = (X509Certificate)ks.getCertificate("testsender");
    PublicKey pubKeySender = sendercert.getPublicKey();
    // 6.2 Verifying the Signature
    Signature myVerifySign = Signature.getInstance("MD5withRSA");
    myVerifySign.initVerify(pubKeySender);
    myVerifySign.update(strMsgToSign.getBytes());
    
    boolean verifySign = myVerifySign.verify(byteSignedData);
    if (verifySign == false)
    {
    	System.out.println(" Error in validating Signature ");
    }
    
    else
    	System.out.println(" Successfully validated Signature ");

    // 7. Decrypt the message using Recv private Key to get the Symmetric Key
    char[] recvpassword = "recv123".toCharArray();
    Key recvKey =  ks.getKey("testrecv", recvpassword);
    PrivateKey recvPrivateKey = (PrivateKey)recvKey;
    
    // Parsing the MessageDigest and the encrypted value
    String strRecvSignedData = new String (byteSignedData);
    String[] strRecvSignedDataArray = new String [10];
    strRecvSignedDataArray = strMsgToSign.split("|");
    int intindexofsep = strMsgToSign.indexOf("|");
    String strEncryptWithPublicKey = strMsgToSign.substring(0,intindexofsep);
    String strHashOfData = strMsgToSign.substring(intindexofsep+1);

    // Decrypting to get the symmetric key
    byte[] bytestrEncryptWithPublicKey = new BASE64Decoder().decodeBuffer(strEncryptWithPublicKey);
    byte[] byteDecryptWithPrivateKey = encryptUtil.decryptData(byteEncryptWithPublicKey,recvPrivateKey,"RSA/ECB/PKCS1Padding");
    
    // 8. Decrypt the data using the Symmetric Key
    javax.crypto.spec.SecretKeySpec secretKeySpecDecrypted = new javax.crypto.spec.SecretKeySpec(byteDecryptWithPrivateKey,"AES");
    byte[] byteDecryptText = encryptUtil.decryptData(byteCipherText,secretKeySpecDecrypted,"AES");
    String strDecryptedText = new String(byteDecryptText);
    System.out.println(" Decrypted data is " +strDecryptedText);
    
    // 9. Compute MessageDigest of data + Signed message
    MessageDigest recvmd = MessageDigest.getInstance("MD5");
    recvmd.update(byteDecryptText);
	byte byteHashOfRecvSignedData[] = recvmd.digest();

	String strHashOfRecvSignedData = new String();
		
	for (int i = 0; i < byteHashOfRecvSignedData.length; i++){
		strHashOfRecvSignedData = strHashOfRecvSignedData + Integer.toHexString((int)byteHashOfRecvSignedData[i] & 0xFF) ;
             }
	// 10. Validate if the Message Digest of the Decrypted Text matches the Message Digest of the Original Message
	if (!strHashOfRecvSignedData.equals(strHashOfData))
	{
		System.out.println(" Message has been tampered ");
	}
	
	}
	
	catch(Exception exp)
	{
		System.out.println(" Exception caught " + exp);
		exp.printStackTrace();
	}
	
	
	}

}

SymmetricEncrypt.java

package org.owasp.crypto;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.Cipher;
import java.security.Key;

import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import java.security.InvalidAlgorithmParameterException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;

import sun.misc.BASE64Encoder;

/**
 * @author Joe Prasanna Kumar
 * This program provides the following cryptographic functionalities
 * 1. Encryption using AES
 * 2. Decryption using AES
 * 
 * High Level Algorithm :
 * 1. Generate a DES key (specify the Key size during this phase) 
 * 2. Create the Cipher 
 * 3. To Encrypt : Initialize the Cipher for Encryption
 * 4. To Decrypt : Initialize the Cipher for Decryption
 * 
 * 
 */

public class SymmetricEncrypt {
			
		String strDataToEncrypt = new String();
		String strCipherText = new String();
		String strDecryptedText = new String();
		static KeyGenerator keyGen;
		private static String strHexVal = "0123456789abcdef";

		public static SecretKey getSecret(){
		/**
		 *  Step 1. Generate an AES key using KeyGenerator
		 *  		Initialize the keysize to 128 
		 * 
		 */
			
			try{
				keyGen = KeyGenerator.getInstance("AES");
				keyGen.init(128);

				}
					
			catch(Exception exp)
			{
				System.out.println(" Exception inside constructor " +exp);
			}
			
			SecretKey secretKey = keyGen.generateKey();
			return secretKey;
		}
		
		/**
		 *  Step2. Create a Cipher by specifying the following parameters
		 * 			a. Algorithm name - here it is AES
		 */
		
		
		public byte[] encryptData(byte[] byteDataToEncrypt, Key secretKey, String Algorithm) {
			byte[] byteCipherText = new byte[200];
			
			try {
			Cipher aesCipher = Cipher.getInstance(Algorithm);
		
		/**
		 *  Step 3. Initialize the Cipher for Encryption 
		 */
			if(Algorithm.equals("AES")){
				aesCipher.init(Cipher.ENCRYPT_MODE,secretKey,aesCipher.getParameters());
				}
				else if(Algorithm.equals("RSA/ECB/PKCS1Padding")){
				aesCipher.init(Cipher.ENCRYPT_MODE,secretKey);
				} 
				
		/**
		 *  Step 4. Encrypt the Data
		 *  		1. Declare / Initialize the Data. Here the data is of type String
		 *  		2. Convert the Input Text to Bytes
		 *  		3. Encrypt the bytes using doFinal method 
		 */
		byteCipherText = aesCipher.doFinal(byteDataToEncrypt); 
		strCipherText = new BASE64Encoder().encode(byteCipherText);

			}
			
			catch (NoSuchAlgorithmException noSuchAlgo)
			{
				System.out.println(" No Such Algorithm exists " + noSuchAlgo);
			}
			
				catch (NoSuchPaddingException noSuchPad)
				{
					System.out.println(" No Such Padding exists " + noSuchPad);
				}
			
					catch (InvalidKeyException invalidKey)
					{
						System.out.println(" Invalid Key " + invalidKey);
					}
					
					catch (BadPaddingException badPadding)
					{
						System.out.println(" Bad Padding " + badPadding);
					}
					
					catch (IllegalBlockSizeException illegalBlockSize)
					{
						System.out.println(" Illegal Block Size " + illegalBlockSize);
						illegalBlockSize.printStackTrace();
					}
					catch (Exception exp)
					{
						exp.printStackTrace();
					}
					
		return byteCipherText;
		}
		/**
		 *  Step 5. Decrypt the Data
		 *  		1. Initialize the Cipher for Decryption 
		 *  		2. Decrypt the cipher bytes using doFinal method 
		 */
		
		public byte[] decryptData(byte[] byteCipherText, Key secretKey, String Algorithm) {
			byte[] byteDecryptedText = new byte[200];
						
			try{	
		Cipher aesCipher = Cipher.getInstance(Algorithm);
		if(Algorithm.equals("AES")){
		aesCipher.init(Cipher.DECRYPT_MODE,secretKey,aesCipher.getParameters());
		}
		else if(Algorithm.equals("RSA/ECB/PKCS1Padding")){
		aesCipher.init(Cipher.DECRYPT_MODE,secretKey);
		} 
		
		byteDecryptedText = aesCipher.doFinal(byteCipherText);
		strDecryptedText = new String(byteDecryptedText);
			}
		
		catch (NoSuchAlgorithmException noSuchAlgo)
		{
			System.out.println(" No Such Algorithm exists " + noSuchAlgo);
		}
		
			catch (NoSuchPaddingException noSuchPad)
			{
				System.out.println(" No Such Padding exists " + noSuchPad);
			}
		
				catch (InvalidKeyException invalidKey)
				{
					System.out.println(" Invalid Key " + invalidKey);
					invalidKey.printStackTrace();
				}
				
				catch (BadPaddingException badPadding)
				{
					System.out.println(" Bad Padding " + badPadding);
					badPadding.printStackTrace();
				}
				
				catch (IllegalBlockSizeException illegalBlockSize)
				{
					System.out.println(" Illegal Block Size " + illegalBlockSize);
					illegalBlockSize.printStackTrace();
				}
				
				catch (InvalidAlgorithmParameterException invalidParam)
				{
					System.out.println(" Invalid Parameter " + invalidParam);
				}
	
		return byteDecryptedText;
		}
		
		
		public static byte[] convertStringToByteArray(String strInput) {
			strInput = strInput.toLowerCase();
			byte[] byteConverted = new byte[(strInput.length() + 1) / 2];
			int j = 0;
			int interimVal;
			int nibble = -1;

			for (int i = 0; i < strInput.length(); ++i) {
				interimVal = strHexVal.indexOf(strInput.charAt(i));
				if (interimVal >= 0) {
					if (nibble < 0) {
						nibble = interimVal;
					} else {
						byteConverted[j++] = (byte) ((nibble << 4) + interimVal);
						nibble = -1;
					}
				}
			}

			if (nibble >= 0) {
				byteConverted[j++] = (byte) (nibble << 4);
			}

			if (j < byteConverted.length) {
				byte[] byteTemp = new byte[j];
				System.arraycopy(byteConverted, 0, byteTemp, 0, j);
				byteConverted = byteTemp;
			}

			return byteConverted;
		}
		
		public static String convertByteArrayToString(byte[] block) {
			StringBuffer buf = new StringBuffer();

			for (int i = 0; i < block.length; ++i) {
				buf.append(strHexVal.charAt((block[i] >>> 4) & 0xf));
				buf.append(strHexVal.charAt(block[i] & 0xf));
			}

			return buf.toString();
		}
}

References

  1. Computer Security Arts and Science – Matt Bishop
  2. Core Security Patterns – Christopher Steele, Ray Lai and Ramesh Nagappan


Traducción Español

Para mayor detalle vea: Traducción Español