What is a message authentication code (MAC)?

A MAC is a type of a message digest but that requires a key. It uses that key to encrypt the hash. Only the receiver, who has access to that key, can decrypt the hash and verify whether the message has not been tampered with.

This is to solve the problem that a man-in-the-middle, one that spies the communication line, intercepts a message with a conventional message digest and then sends out another message with another message digest. The receiver is unable to know whether the whole thing comes from the original sender. If the sender and receiver would use a MAC utilizing a (secret) key, there would be no way for the man-in-the-middle to intercept and send out a new message as he doesn’t know the key.

The problem with MACs is that secret keys need to be shared between both parties.

A MAC works with available hashing methods (MD5 or SHA).

Programmatically generating a public and private key

Programmatically:

Main.java:

import javax.crypto.spec.*;
import java.security.*;
import javax.crypto.*;
import java.io.*;
 
public class Main
{
   public static void main(String []args) throws Exception {
      KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
      kpg.initialize(1024);
      KeyPair kp = kpg.genKeyPair();
 
      byte[] publicKey = kp.getPublic().getEncoded();
      byte[] privateKey = kp.getPrivate().getEncoded();
 
      FileOutputStream fos = new FileOutputStream("public.key");
      fos.write(publicKey);
      fos.close();
      fos = new FileOutputStream("private.key");
      fos.write(privateKey);
      fos.close();
   }
}

using keytool:

keytool -genkey -alias mykey -keysize 1024 -keyalg RSA

What is a keystore?

A keystore is a database (usually a file) that can contain trusted certificates and combinations of private keys with their corresponding certficiates.

- trusted certificates: these are certificates from the entities you trust, for example a certificate from Thawte. Trusted certificates are used to validate other certificates. For example, suppose you have a certificate A signed by Thawte and you want to check it for trustworthiness. Certificate A contains: a public key, some identification information about the certificate (name, etc.), a digital signature (calculated by the one that is vouching for the certificate, in this case Thawte), and some identification information about the voucher. Now you can extract the digital signature from A and decrypt it with the public key from the Thawte (stored in the keystore as a trusted certificate) to check the validity the public key of A.

- private keys/certificates: each is a public key certificate with their corresponding private keys.

To create a keystore containing a self-signed certificate:

C:>c:jdk1.3binkeytool -genkey -alias mykey -keyalg RSA
Enter keystore password:  esuspass
What is your first and last name?
  [Unknown]:  Joris Van den Bogaert
What is the name of your organizational unit?
  [Unknown]:  ESUS Team
What is the name of your organization?
  [Unknown]:  Esus, Inc
What is the name of your City or Locality?
  [Unknown]:  Meerbeek
What is the name of your State or Province?
  [Unknown]:
What is the two-letter country code for this unit?
  [Unknown]:  BE
Is <CN=Joris Van den Bogaert, OU=ESUS Team, O="Esus, Inc", L=Meerbeek, ST=Unknow
n, C=BE> correct?
  [no]:  yes

Enter key password for <mykey>
        (RETURN if same as keystore password):
 
C:> c:jdk1.3binkeytool -list
Enter keystore password:  esuspass

Keystore type: jks
Keystore provider: SUN

Your keystore contains 1 entry:

mykey, Mon Aug 06 13:29:28 CEST 2001, keyEntry,
Certificate fingerprint (MD5): 0E:8F:ED:F3:E3:07:25:9C:1D:15:65:43:7C:4F:86:32

The keystore containing the trusted certificates is located at JRE_HOME/lib/security/cacerts. You
can list its contents:

C:> c:jdk1.3binkeytool -list -keystore c:jdk1.3jrelibsecuritycacerts
Enter keystore password:

*****************  WARNING WARNING WARNING  *****************
* The integrity of the information stored in your keystore  *
* has NOT been verified!  In order to verify its integrity, *
* you must provide your keystore password.                  *
*****************  WARNING WARNING WARNING  *****************

Keystore type: jks
Keystore provider: SUN

Your keystore contains 10 entries:

thawtepersonalfreemailca, Fri Feb 12 21:12:16 CET 1999, trustedCertEntry,
Certificate fingerprint (MD5): 1E:74:C3:86:3C:0C:35:C5:3E:C2:7F:EF:3C:AA:3C:D9
thawtepersonalbasicca, Fri Feb 12 21:11:01 CET 1999, trustedCertEntry,
Certificate fingerprint (MD5): E6:0B:D2:C9:CA:2D:88:DB:1A:71:0E:4B:78:EB:02:41
verisignclass3ca, Mon Jun 29 19:05:51 CEST 1998, trustedCertEntry,
Certificate fingerprint (MD5): 78:2A:02:DF:DB:2E:14:D5:A7:5F:0A:DF:B6:8E:9C:5D
thawtepersonalpremiumca, Fri Feb 12 21:13:21 CET 1999, trustedCertEntry,
Certificate fingerprint (MD5): 3A:B2:DE:22:9A:20:93:49:F9:ED:C8:D2:8A:E7:68:0D
thawteserverca, Fri Feb 12 21:14:33 CET 1999, trustedCertEntry,
Certificate fingerprint (MD5): C5:70:C4:A2:ED:53:78:0C:C8:10:53:81:64:CB:D0:1D
verisignclass4ca, Mon Jun 29 19:06:57 CEST 1998, trustedCertEntry,
Certificate fingerprint (MD5): 1B:D1:AD:17:8B:7F:22:13:24:F5:26:E2:5D:4E:B9:10
verisignserverca, Mon Jun 29 19:07:34 CEST 1998, trustedCertEntry,
Certificate fingerprint (MD5): 74:7B:82:03:43:F0:00:9E:6B:B3:EC:47:BF:85:A5:93
verisignclass1ca, Mon Jun 29 19:06:17 CEST 1998, trustedCertEntry,
Certificate fingerprint (MD5): 51:86:E8:1F:BC:B1:C3:71:B5:18:10:DB:5F:DC:F6:20
thawtepremiumserverca, Fri Feb 12 21:15:26 CET 1999, trustedCertEntry,
Certificate fingerprint (MD5): 06:9F:69:79:16:66:90:02:1B:8C:8C:A2:C3:07:6F:3A
verisignclass2ca, Mon Jun 29 19:06:39 CEST 1998, trustedCertEntry,
Certificate fingerprint (MD5): EC:40:7D:2B:76:52:67:05:2C:EA:F2:3A:4F:65:F0:D8

Generating a MAC that uses MD5 in Java

The following example generates a random key of 128 bits (16 bytes) necessary for HmacMD5 and computes a hash using this key. The key would have to be shared between sender and receiver.

Main.java:

import javax.crypto.spec.*;
import javax.crypto.Mac;
import java.security.*;
import javax.crypto.*;
import sun.misc.*;
 
public class Main
{
   public static void main(String []args) throws Exception {
      String message = "the sun is green and the grass shines";
 
      byte[] b = generateMAC(message);
      System.out.println("HMAC-MD5 for message '" + message + "':");
      System.out.println("t" + new BASE64Encoder().encode(generateMAC(message)));
   }
 
   public static byte[] generateMAC(String message) throws Exception {
      // generate key
      SecureRandom sr = new SecureRandom();
      byte[] b = new byte[20];
      sr.nextBytes(b);
      SecretKey key = new SecretKeySpec(b, "HmacMD5");
 
      // generate message digest based on key
      Mac mac = Mac.getInstance("HmacMD5");
      mac.init(key);
 
      return mac.doFinal(message.getBytes());
   }
}

outputs:

HMAC-MD5 for message 'the sun is green and the grass shines':
        JX25ruAB1qM0w39rTQnPGA==

Encrypting/decrypting a message using an assymetric encryption method (eg. RSA)

The following code demonstrates using a pair of RSA public/private key pairs to secure a message between a producer and a consumer. It also generates and validates a signature along the way since that is another common task.
AsymetricTest.java:

import org.bouncycastle.jce.provider.BouncyCastleProvider;
 
import java.security.*;
import javax.crypto.*;
 
public class AsymetricTest {
 
    // these algorithms need to work together!
    public static String keyAlgorithm = "RSA" ;
    public static String cipherAlgorithm = "RSA/OAEP" ;
    public static String signatureAlgorithm = "SHA1WithRSAEncryption" ;
 
    static public void main(String[] args) {
	String testData = "Hugh, and only Hugh, can prevent florist friars." ;
 
	try {
	    // make sure the BC provider is registered.
	    Security.addProvider(new BouncyCastleProvider());
 
	    /***
	     * Generate two pairs of keys, one for the producer
	     * and one for the consumer.
	     ***/
	    KeyPairGenerator gen = KeyPairGenerator.getInstance(keyAlgorithm, "BC");
	    gen.initialize(1024, new SecureRandom());
 
	    System.out.println("Generating keys . . .") ;
	    KeyPair producerPair = gen.generateKeyPair();
	    KeyPair consumerPair = gen.generateKeyPair();
	    System.out.println("Generated keys, proceeding") ;
 
	    /***
	     * first on the producer side: encrypt and sign the
	     * original data.  The producer knows the consumer public key.
	     ***/
 
	    // encrypt the testData using the public key
	    byte[] encryptedData = encrypt(testData.getBytes(), consumerPair.getPublic()) ;
 	    
	    System.out.println("Encrypted byte count: " + encryptedData.length) ;
 
	    // generate a digital signature
	    byte[] dataSignature = generateSignature(testData, producerPair.getPrivate()) ;
 
	    System.out.println("Signature created, bytes: " + dataSignature.length) ;
 
	    /***
	     * uncomment one of these lines to befoul either the
	     * encrypted data or the signature "in transit" if you want
	     * to see a failure in the next steps.
	     ***/
	    //encryptedData[8] = 0 ;
	    //dataSignature[8] = 0 ;
 	    
	    /***
	     * and now on the consumer side: decrypt the original data,
	     * then use that result to validate the signature.  The
	     * consumer knows the producer's public key.
	     ***/
 
	    // decrypt the message using the private key
	    byte[] receivedData = decrypt(encryptedData, consumerPair.getPrivate()) ;
	    System.out.println("Data decoded, byte count: " + receivedData.length) ;
	    System.out.println("[" + new String(receivedData) + "]") ;
 
	    // and validate the signature
	    if ( ! validateSignature(receivedData, dataSignature, producerPair.getPublic()) ) {
		throw new SignatureException("Signature validation failed") ;
		}
 
	    System.out.println("Signature OK.") ;
	    }
	catch ( Exception ex ) {
	    ex.printStackTrace() ;
	    }
 	    
	System.exit(0);
	}

    public static byte[] encrypt(byte[] toEncrypt, PublicKey key)
				    throws GeneralSecurityException {
	Cipher cipher = Cipher.getInstance(cipherAlgorithm) ;
	cipher.init(Cipher.ENCRYPT_MODE, key) ;
 
	byte[] result = cipher.doFinal(toEncrypt) ;
	return result ;
    }

    public static byte[] decrypt(byte[] toDecrypt, PrivateKey key)
				    throws GeneralSecurityException {
	Cipher deCipher = Cipher.getInstance(cipherAlgorithm) ;
	deCipher.init(Cipher.DECRYPT_MODE, key) ;
 
	deCipher.update(toDecrypt) ;
 
	byte[] result = deCipher.doFinal() ;
	return result ;
    }

    public static byte[] generateSignature(String toSign, PrivateKey key)
				    throws GeneralSecurityException {
	Signature genSig = Signature.getInstance(signatureAlgorithm) ;
	genSig.initSign(key) ;
 
	genSig.update(toSign.getBytes()) ;
 
	byte[] result = genSig.sign() ;
	return result ;
    }

    public static boolean validateSignature(byte[] dataToValidate, byte[] sigToValidate, PublicKey key)
				    throws GeneralSecurityException {
	Signature valSig = Signature.getInstance(signatureAlgorithm) ;
	valSig.initVerify(key) ;
 
	valSig.update(dataToValidate) ;
 
	return valSig.verify(sigToValidate) ;
	}
    }

(tested with the BouncyCastle JCE provider, http://www.bouncycastle.org)

Getting a certificate from a Java keystore

I assumed here that you created the alias in the default keystore described in the question What is a keystore?.

Programmatically:

Main.java:

import java.security.cert.Certificate;
import java.security.*;
import java.io.*;
 
public class Main {
   public static void main(String []args) {
      try {
         KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
     
         InputStream in = new FileInputStream(System.getProperty("user.home") + "\" + 
                                              ".keystore");
 
         keyStore.load(in, "esuspass".toCharArray());
     
         Certificate cert = keyStore.getCertificate("mykey");
 
         System.out.println(cert);
      }  
      catch (KeyStoreException e) { 
         System.out.println(e);
      } 
      catch (java.security.cert.CertificateException e) {
         System.out.println(e);
      } 
      catch (NoSuchAlgorithmException e) {
         System.out.println(e);
      } 
      catch (java.io.IOException e) {
         System.out.println(e);
      } 
   }
}

outputs:

C:> java Main
[
[
  Version: V1
  Subject: CN=Joris Van den Bogaert, OU=ESUS Team, O="Esus, Inc", L=Meerbeek, ST
=Unknown, C=BE
  Signature Algorithm: MD5withRSA, OID = 1.2.840.113549.1.1.4

  Key:  RSA Public Key
            modulus: 10001
    public exponent: d395abbc7bd76b47c60a5e6e30cb7b74d8878706fe757d063daf737d8cd
3bf70447eaefd7ebaaab4d267898d8cd0680983a69b2e78c1d14ec27bfcff04da068e2dded11e323
4ecb900955295f96036ed0ddb919c5036bdb422415af60d81b793dbb40709f3fdd01609c470a52c5
e9ddc6879649122df5591d6f95efdbdf95889

  Validity: [From: Mon Aug 06 13:29:25 CEST 2001,
               To: Sun Nov 04 12:29:25 CET 2001]
  Issuer: CN=Joris Van den Bogaert, OU=ESUS Team, O="Esus, Inc", L=Meerbeek, ST=
Unknown, C=BE
  SerialNumber: [    3b6e7f95 ]

]
  Algorithm: [MD5withRSA]
  Signature:
0000: 94 19 99 85 71 95 6B C6   20 A0 42 C9 0E D1 4C D0  ....q.k. .B...L.
0010: 7A D6 30 38 4A C7 9E 65   72 F0 63 85 3D 46 74 48  z.08J..er.c.=FtH
0020: 7D 1D 70 A4 0A C9 A2 FC   CB 01 04 FA C5 31 39 1A  ..p..........19.
0030: 9C DC EA 07 BD 2C AD 7C   B0 D3 8E 00 16 90 E4 0E  .....,..........
0040: DB 73 3A AC 15 9F 6D 3F   7E 61 E6 E1 10 E5 A7 15  .s:...m?.a......
0050: 1C F5 B2 88 60 4C 55 3D   6B 38 22 B8 12 D6 B3 AF  ....`LU=k8".....
0060: 3A 81 61 C1 8C B3 CC 33   18 20 CA C8 B7 18 15 88  :.a....3. ......
0070: 67 6A C4 F7 D4 95 7E 80   7F DC F6 C6 79 36 86 C2  gj..........y6..

]

Using keytool:

C:> c:jdk1.3binkeytool -export -alias mykey -file mykey.cer
Enter keystore password:  esuspass
Certificate stored in file <mykey.cer>

Generating a MAC that uses SHA-1

The following example consists of a Sender and a Receiver.

- The Sender creates a message, generates a secret key, generates a mac given the message and the key and writes both the message and the mac to the file “message.ser”. Typically, this would be send to the other party.
A MAC works with a secret key. The weak point is that both the sender and the receiver must know the secret key. In the example, the secret key is stored in a keystore “keystore.kst” in the current directory.

- The Receiver reads “message.ser” and extracts the message and the MAC. To find out whether the message was tampered with, it calculates the MAC again using the message and the secret key that it reads out from “keystore.kst”.

Note that to use the keystore for secret keys, the keystore algorithm “JCEKS” must be used. “JCEKS” is implemented by the security provider that comes with JCE. You can download it here.

I added jce1_2_2.jar and sunjce_provider to my classpath. The code is documented.

Sender.java:

import javax.crypto.spec.*;
import javax.crypto.Mac;
import java.security.*;
import javax.crypto.*;
import sun.misc.*;
import java.io.*;
 
public class Sender
{
   public static void main(String []args) throws Exception {
      // message to send 
      String message = "the sun is green and the grass shines";
 
      // generate a random key and store it in keystore.kst in current directory 
      SecretKey key = generateAndStoreKey();
 
      // generate a MAC for the message, given the secret key
      byte[] mac = generateMAC(message, key);
      
      System.out.println("HMAC-MD5 for message '" + message + "':");
      System.out.println("t" + new BASE64Encoder().encode(mac));
      System.out.println("Writing message+mac to file 'message.ser'");
      
      // writing both the message and the mac to message.ser
      ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("message.ser"));
      oos.writeObject(message);
      oos.writeObject(mac);
      oos.close();
   }
 
   public static SecretKey generateAndStoreKey() throws Exception {
      KeyStore ks = KeyStore.getInstance("JCEKS");
      // initiaze new KeyStore, must do!
      ks.load(null, null);
 
      // generate random key
      SecureRandom sr = new SecureRandom();
      byte[] b = new byte[20];
      sr.nextBytes(b);
      SecretKey key = new SecretKeySpec(b, "HmacSHA1");
 
      // save secret key in our new keystore with alias MySecretKey
      FileOutputStream fos = new FileOutputStream("keystore.kst");
      ks.setKeyEntry("MySecretKey", key, "123456".toCharArray(), null);
      ks.store(fos, "123456".toCharArray());
      fos.close();
 
      return key;
   }
 
   public static byte[] generateMAC(String message, SecretKey key) throws Exception {
      // generate message digest based on key
      Mac mac = Mac.getInstance("HmacMD5");
      mac.init(key);
 
      return mac.doFinal(message.getBytes());
   }
}

Receiver.java:

import javax.crypto.spec.*;
import javax.crypto.Mac;
import java.security.*;
import javax.crypto.*;
import sun.misc.*;
import java.io.*;
 
public class Receiver
{
   public static void main(String []args) throws Exception {
      // read in message and mac from message.ser
      ObjectInputStream ois = new ObjectInputStream(new FileInputStream("message.ser"));
      String message = (String) ois.readObject();
      byte[] mac = (byte[]) ois.readObject();
 
      System.out.println("Original message: " + message);
 
      // get secret key from keystore keystore.kst
      SecretKey key = getSecretKey();
 
      // generate mac again given this key
      byte[] newMac = getMAC(message, key);
 
      // ensure the new mac is equal to the one that was "sent" to us
      if (MessageDigest.isEqual(mac, newMac)) {
         System.out.println("Message was not tampered with!");
      }
      else {
         System.out.println("Message has been tampered with!");
      }
   }
 
   public static byte[] getMAC(String message, SecretKey key) throws Exception {
      // generate message digest based on key
      Mac mac = Mac.getInstance("HmacMD5");
      mac.init(key);
  
      return mac.doFinal(message.getBytes());
   }

   public static SecretKey getSecretKey() throws Exception {
      FileInputStream fis = new FileInputStream("keystore.kst");
      KeyStore ks = KeyStore.getInstance("JCEKS");
      ks.load(fis, "123456".toCharArray());
 
      SecretKey key = (SecretKey) ks.getKey("MySecretKey", "123456".toCharArray());
  
      return key;
   }
}

sample run:

C:mybouncycastlemacsha1>java Sender
HMAC-MD5 for message 'the sun is green and the grass shines':
        XmvNl18rRj9ql4JkS3N8vw==
Writing message+mac to file 'message.ser'
 
C:mybouncycastlemacsha1>java Receiver
Original message: the sun is green and the grass shines
Message was not tampered with!

What is hybrid cryptography?

Symmetric and asymmetric ciphers each have their own advantages and disadvantages. Symmetric ciphers are significantly faster (Schneier states “at least 1000 times faster”) than asymmetric ciphers, but require all parties to somehow share a secret (the key). The asymmetric algorithms allow public key infrastructures and key exchange systems, but at the cost of speed.

A hybrid cryptosystem is a protocol using multiple ciphers of different types together, each to it’s best advantage. One common approach is to generate a random secret key for a symmetric cipher, and then encrypt this key via an asymmentric cipher using the recipient’s public key. The message itself is then encrypted using the symmetric cipher and the secret key. Both the encrypted secret key and the encrypted message are then sent to the recipient.

The recipient decrypts the secret key first, using his/her own private key, and then uses that key to decrypt the message. This is basically the approach used in PGP.

Debugging permissions set by policy files

You can use the option

   -Djava.security.debug=<PARAM>
 
where <PARAM> is one of the following
   all       (turn on all debugging)
   access    (print all checkPermission results)
   jar       (jar verification)
   policy    (loading and granting)
   scl	 (permission SecureClassLoader assigns) 

The following can be used with access (for example access.domain):

   stack	 (include stack trace) 
   domain	 (dumps all domains in context)
   failure   (before throwing exception, dump stack
              and domain that didn't have permission)

When you’re testing policy problems with the Java plug-in, you can go the the plug-in control panel and to Java Runtime Parameters:

   -Djava.security.debug=<PARAM>

However, when I tried this out (with Java plug-in) it crashed the browser (IE5.5) on several params. The parameter policy worked and gave me sufficient results to solve my problem.

Encrypting/decrypting a message using a symmetric encryption method (eg. DES)

Eric Young has written an implementation of DES. Here are the source files:

Des.java
DesCrypt.java
DesKey.java
Int32Manipulator.java

The Des.java file has been slightly modified (added a memset method to ensure 8 bytes are used for the key).

Here’s an example on how to use these classes:

public class Main
{
   public static void main(String []args) {
      // key (8 bytes) 
      String key = "12345678";
 
      // encrypt using Eric Young's DES Java implementation
      byte[] de = Des.encrypt(key, "Esus.com, aiming to provide the largest Java directory on the web");
 
      // decrypt 
      String s = Des.decrypt(key, de);
      System.out.println(s);
   }
}