Java tutorial
/* * JBoss, Home of Professional Open Source. * Copyright 2012, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.picketbox.json.enc; import static org.picketbox.json.PicketBoxJSONConstants.COMMON.PERIOD; import java.security.PrivateKey; import java.security.PublicKey; import java.util.UUID; import javax.crypto.Cipher; import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.json.JSONException; import org.picketbox.json.PicketBoxJSONMessages; import org.picketbox.json.exceptions.ProcessingException; import org.picketbox.json.util.Base64; import org.picketbox.json.util.PicketBoxJSONUtil; /** * Represents JSON Web Encryption http://tools.ietf.org/html/draft-jones-json-web-encryption * * @author anil saldhana * @since Jul 27, 2012 */ public class JSONWebEncryption { protected JSONWebEncryptionHeader jsonWebEncryptionHeader; /** * Create an attached Header * * @return */ public JSONWebEncryptionHeader createHeader() { if (jsonWebEncryptionHeader == null) { jsonWebEncryptionHeader = new JSONWebEncryptionHeader(); } return jsonWebEncryptionHeader; } /** * Get the {@link JSONWebEncryptionHeader} * * @return */ public JSONWebEncryptionHeader getJsonWebEncryptionHeader() { return jsonWebEncryptionHeader; } /** * Set the {@link JSONWebEncryptionHeader} * * @param jsonWebEncryptionHeader */ public void setJsonWebEncryptionHeader(JSONWebEncryptionHeader jsonWebEncryptionHeader) { this.jsonWebEncryptionHeader = jsonWebEncryptionHeader; } /** * Encrypt * * @param plainText * @param recipientPublicKey * @return * @throws ProcessingException */ public String encrypt(String plainText, PublicKey recipientPublicKey) throws ProcessingException { if (jsonWebEncryptionHeader == null) { throw PicketBoxJSONMessages.MESSAGES.jsonEncryptionHeaderMissing(); } if (plainText == null) { throw PicketBoxJSONMessages.MESSAGES.invalidNullArgument("plainText"); } if (recipientPublicKey == null) { throw PicketBoxJSONMessages.MESSAGES.invalidNullArgument("recipientPublicKey"); } byte[] contentMasterKey = createContentMasterKey(); return encrypt(plainText, recipientPublicKey, contentMasterKey); } /** * Encrypt * * @param plainText * @param recipientPublicKey * @param contentMasterKey * @return * @throws ProcessingException */ public String encrypt(String plainText, PublicKey recipientPublicKey, byte[] contentMasterKey) throws ProcessingException { if (jsonWebEncryptionHeader == null) { throw PicketBoxJSONMessages.MESSAGES.jsonEncryptionHeaderMissing(); } if (plainText == null) { throw PicketBoxJSONMessages.MESSAGES.invalidNullArgument("plainText"); } if (recipientPublicKey == null) { throw PicketBoxJSONMessages.MESSAGES.invalidNullArgument("recipientPublicKey"); } if (contentMasterKey == null) { return encrypt(plainText, recipientPublicKey); } SecretKey contentEncryptionKey = new SecretKeySpec(contentMasterKey, EncUtil.AES); // Encrypt using Recipient's public key to yield JWE Encrypted Key byte[] jweEncryptedKey = encryptKey(recipientPublicKey, contentMasterKey); String encodedJWEKey = PicketBoxJSONUtil.b64Encode(jweEncryptedKey); StringBuilder builder = new StringBuilder(PicketBoxJSONUtil.b64Encode(jsonWebEncryptionHeader.toString())); builder.append(PERIOD); builder.append(encodedJWEKey); if (jsonWebEncryptionHeader.needIntegrity()) { int cekLength = jsonWebEncryptionHeader.getCEKLength(); byte[] cek = generateCEK(contentEncryptionKey.getEncoded(), cekLength); // Deal with IV String iv; try { iv = jsonWebEncryptionHeader.getDelegate().getString("iv"); } catch (JSONException e) { throw PicketBoxJSONMessages.MESSAGES.ignorableError(e); } IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes()); byte[] encryptedText = EncUtil.encryptUsingAES_CBC(plainText, cek, ivParameterSpec); String encodedJWEText = PicketBoxJSONUtil.b64Encode(encryptedText); builder.append(PERIOD); builder.append(encodedJWEText); int cikLength = jsonWebEncryptionHeader.getCIKLength(); byte[] cik = generateCIK(contentEncryptionKey.getEncoded(), cikLength); byte[] integrityValue = performMac(cik, builder.toString().getBytes()); String encodedIntegrityValue = PicketBoxJSONUtil.b64Encode(integrityValue); builder.append(PERIOD); builder.append(encodedIntegrityValue); } else { // Encrypt the plain text byte[] encryptedText = encryptText(plainText, recipientPublicKey); String encodedJWEText = PicketBoxJSONUtil.b64Encode(encryptedText); builder.append(PERIOD); builder.append(encodedJWEText); } return builder.toString(); } /** * Decrypt using a {@link PrivateKey} * * @param encryptedText * @param privateKey * @return * @throws ProcessingException */ public String decrypt(String encryptedText, PrivateKey privateKey) throws ProcessingException { if (privateKey == null) { throw PicketBoxJSONMessages.MESSAGES.invalidNullArgument("privateKey"); } try { String[] splitBits = encryptedText.split("\\."); int length = splitBits.length; String encodedHeader = splitBits[0]; String encodedKey = splitBits[1]; String encodedValue = splitBits[2]; String encodedIntegrity = null; if (length == 4) { encodedIntegrity = splitBits[3]; } String decodedHeader = new String(Base64.decode(encodedHeader)); JSONWebEncryptionHeader header = new JSONWebEncryptionHeader(); header.load(decodedHeader); if (header.needIntegrity()) { byte[] decodedKey = Base64.decode(encodedKey); byte[] secretKey = decryptKey(privateKey, decodedKey); int cekLength = header.getCEKLength(); byte[] cek = generateCEK(secretKey, cekLength); // Deal with IV String iv; try { iv = header.getDelegate().getString("iv"); } catch (JSONException e) { throw PicketBoxJSONMessages.MESSAGES.ignorableError(e); } IvParameterSpec ivParameter = new IvParameterSpec(iv.getBytes()); byte[] decodedText = Base64.decode(encodedValue); byte[] plainText = EncUtil.decryptUsingAES_CBC(decodedText, cek, ivParameter); int cikLength = header.getCIKLength(); byte[] cik = generateCIK(secretKey, cikLength); StringBuilder builder = new StringBuilder(PicketBoxJSONUtil.b64Encode(header.toString())); builder.append(PERIOD).append(encodedKey).append(PERIOD).append(encodedValue); byte[] integrityValue = performMac(cik, builder.toString().getBytes()); String encodedIntegrityValue = PicketBoxJSONUtil.b64Encode(integrityValue); if (byteEquals(encodedIntegrityValue.getBytes(), encodedIntegrity.getBytes())) { return new String(plainText); } else { throw new RuntimeException("Integrity Checks Failed"); } } Cipher textCipher = header.getCipherBasedOnAlg(); textCipher.init(Cipher.DECRYPT_MODE, privateKey); byte[] decodedText = Base64.decode(encodedValue); byte[] plainText = textCipher.doFinal(decodedText); return new String(plainText); } catch (Exception e) { throw PicketBoxJSONMessages.MESSAGES.processingException(e); } } private byte[] encryptText(String plainText, PublicKey recipientPublicKey) throws ProcessingException { if (recipientPublicKey == null) { throw PicketBoxJSONMessages.MESSAGES.invalidNullArgument("recipientPublicKey"); } try { Cipher cipher = jsonWebEncryptionHeader.getCipherBasedOnAlg(); cipher.init(Cipher.ENCRYPT_MODE, recipientPublicKey); return cipher.doFinal(plainText.getBytes()); } catch (Exception e) { throw PicketBoxJSONMessages.MESSAGES.processingException(e); } } private byte[] encryptKey(PublicKey publicKey, byte[] contentMasterKey) throws ProcessingException { if (publicKey == null) { throw PicketBoxJSONMessages.MESSAGES.invalidNullArgument("publicKey"); } try { Cipher cipher = jsonWebEncryptionHeader.getCipherBasedOnAlg(); cipher.init(Cipher.ENCRYPT_MODE, publicKey); return cipher.doFinal(contentMasterKey); } catch (Exception e) { throw PicketBoxJSONMessages.MESSAGES.processingException(e); } } private byte[] decryptKey(PrivateKey privateKey, byte[] encryptedKey) throws ProcessingException { if (privateKey == null) { throw PicketBoxJSONMessages.MESSAGES.invalidNullArgument("privateKey"); } try { Cipher cipher = jsonWebEncryptionHeader.getCipherBasedOnAlg(); cipher.init(Cipher.DECRYPT_MODE, privateKey); return cipher.doFinal(encryptedKey); } catch (Exception e) { throw PicketBoxJSONMessages.MESSAGES.processingException(e); } } /** * Generate a random byte array. * * @return */ private byte[] createContentMasterKey() { return UUID.randomUUID().toString().getBytes(); } /** * Content Integrity Key (CIK) A key used with a MAC function to ensure the integrity of the Ciphertext and the parameters * used to create it. * * @param keyBytes * @param cikByteLength * @return * @throws ProcessingException */ private byte[] generateCIK(byte[] keyBytes, int cikByteLength) throws ProcessingException { // "Integrity" final byte[] otherInfo = { 73, 110, 116, 101, 103, 114, 105, 116, 121 }; ConcatenationKeyDerivation kdfGen = new ConcatenationKeyDerivation(EncUtil.SHA_256); return kdfGen.concatKDF(keyBytes, cikByteLength, otherInfo); } /** * Content Encryption Key (CEK) A symmetric key used to encrypt the Plaintext for the recipient to produce the Ciphertext. * * @param keyBytes * @param cekByteLength * @return * @throws ProcessingException */ private byte[] generateCEK(byte[] keyBytes, int cekByteLength) throws ProcessingException { // "Encryption" final byte[] otherInfo = { 69, 110, 99, 114, 121, 112, 116, 105, 111, 110 }; ConcatenationKeyDerivation kdfGen = new ConcatenationKeyDerivation(EncUtil.SHA_256); return kdfGen.concatKDF(keyBytes, cekByteLength, otherInfo); } private byte[] performMac(byte[] key, byte[] data) throws ProcessingException { Mac mac = null; try { mac = Mac.getInstance(jsonWebEncryptionHeader.getMessageAuthenticationCodeAlgo()); mac.init(new SecretKeySpec(key, mac.getAlgorithm())); mac.update(data); return mac.doFinal(); } catch (Exception e) { throw PicketBoxJSONMessages.MESSAGES.processingException(e); } } private boolean byteEquals(byte[] b1, byte[] b2) { // Check if the addresses match if (b1 == b2) { return true; } // Check if either one is null if (b1 == null || b2 == null) { return false; } // Match on the lengths if (b1.length != b2.length) { return false; } // Match each byte int notMatching = 0; for (int index = 0; index != b1.length; index++) { notMatching |= (b1[index] ^ b2[index]); } return notMatching == 0; } }