mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-08 12:37:37 +00:00
241 lines
6.7 KiB
Java
241 lines
6.7 KiB
Java
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
package org.mozilla.gecko.sync.crypto;
|
|
|
|
import java.security.GeneralSecurityException;
|
|
import java.security.InvalidKeyException;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.util.Arrays;
|
|
|
|
import javax.crypto.BadPaddingException;
|
|
import javax.crypto.Cipher;
|
|
import javax.crypto.IllegalBlockSizeException;
|
|
import javax.crypto.Mac;
|
|
import javax.crypto.NoSuchPaddingException;
|
|
import javax.crypto.spec.IvParameterSpec;
|
|
import javax.crypto.spec.SecretKeySpec;
|
|
|
|
import org.mozilla.apache.commons.codec.binary.Base64;
|
|
|
|
/*
|
|
* All info in these objects should be decoded (i.e. not BaseXX encoded).
|
|
*/
|
|
public class CryptoInfo {
|
|
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
|
|
private static final String KEY_ALGORITHM_SPEC = "AES";
|
|
|
|
private byte[] message;
|
|
private byte[] iv;
|
|
private byte[] hmac;
|
|
private KeyBundle keys;
|
|
|
|
/**
|
|
* Return a CryptoInfo with given plaintext encrypted using given keys.
|
|
*/
|
|
public static CryptoInfo encrypt(byte[] plaintextBytes, KeyBundle keys) throws CryptoException {
|
|
CryptoInfo info = new CryptoInfo(plaintextBytes, keys);
|
|
info.encrypt();
|
|
return info;
|
|
}
|
|
|
|
/**
|
|
* Return a CryptoInfo with given plaintext encrypted using given keys and initial vector.
|
|
*/
|
|
public static CryptoInfo encrypt(byte[] plaintextBytes, byte[] iv, KeyBundle keys) throws CryptoException {
|
|
CryptoInfo info = new CryptoInfo(plaintextBytes, iv, null, keys);
|
|
info.encrypt();
|
|
return info;
|
|
}
|
|
|
|
/**
|
|
* Return a CryptoInfo with given ciphertext decrypted using given keys and initial vector, verifying that given HMAC validates.
|
|
*/
|
|
public static CryptoInfo decrypt(byte[] ciphertext, byte[] iv, byte[] hmac, KeyBundle keys) throws CryptoException {
|
|
CryptoInfo info = new CryptoInfo(ciphertext, iv, hmac, keys);
|
|
info.decrypt();
|
|
return info;
|
|
}
|
|
|
|
/*
|
|
* Constructor typically used when encrypting.
|
|
*/
|
|
public CryptoInfo(byte[] message, KeyBundle keys) {
|
|
this.setMessage(message);
|
|
this.setKeys(keys);
|
|
}
|
|
|
|
/*
|
|
* Constructor typically used when decrypting.
|
|
*/
|
|
public CryptoInfo(byte[] message, byte[] iv, byte[] hmac, KeyBundle keys) {
|
|
this.setMessage(message);
|
|
this.setIV(iv);
|
|
this.setHMAC(hmac);
|
|
this.setKeys(keys);
|
|
}
|
|
|
|
public byte[] getMessage() {
|
|
return message;
|
|
}
|
|
|
|
public void setMessage(byte[] message) {
|
|
this.message = message;
|
|
}
|
|
|
|
public byte[] getIV() {
|
|
return iv;
|
|
}
|
|
|
|
public void setIV(byte[] iv) {
|
|
this.iv = iv;
|
|
}
|
|
|
|
public byte[] getHMAC() {
|
|
return hmac;
|
|
}
|
|
|
|
public void setHMAC(byte[] hmac) {
|
|
this.hmac = hmac;
|
|
}
|
|
|
|
public KeyBundle getKeys() {
|
|
return keys;
|
|
}
|
|
|
|
public void setKeys(KeyBundle keys) {
|
|
this.keys = keys;
|
|
}
|
|
|
|
/*
|
|
* Generate HMAC for given cipher text.
|
|
*/
|
|
public static byte[] generatedHMACFor(byte[] message, KeyBundle keys) throws NoSuchAlgorithmException, InvalidKeyException {
|
|
Mac hmacHasher = HKDF.makeHMACHasher(keys.getHMACKey());
|
|
return hmacHasher.doFinal(Base64.encodeBase64(message));
|
|
}
|
|
|
|
/*
|
|
* Return true if generated HMAC is the same as the specified HMAC.
|
|
*/
|
|
public boolean generatedHMACIsHMAC() throws NoSuchAlgorithmException, InvalidKeyException {
|
|
byte[] generatedHMAC = generatedHMACFor(getMessage(), getKeys());
|
|
byte[] expectedHMAC = getHMAC();
|
|
return Arrays.equals(generatedHMAC, expectedHMAC);
|
|
}
|
|
|
|
/**
|
|
* Performs functionality common to both encryption and decryption.
|
|
*
|
|
* @param cipher
|
|
* @param inputMessage non-BaseXX-encoded message
|
|
* @return encrypted/decrypted message
|
|
* @throws CryptoException
|
|
*/
|
|
private static byte[] commonCrypto(Cipher cipher, byte[] inputMessage)
|
|
throws CryptoException {
|
|
byte[] outputMessage = null;
|
|
try {
|
|
outputMessage = cipher.doFinal(inputMessage);
|
|
} catch (IllegalBlockSizeException e) {
|
|
throw new CryptoException(e);
|
|
} catch (BadPaddingException e) {
|
|
throw new CryptoException(e);
|
|
}
|
|
return outputMessage;
|
|
}
|
|
|
|
/**
|
|
* Encrypt a CryptoInfo in-place.
|
|
*
|
|
* @throws CryptoException
|
|
*/
|
|
public void encrypt() throws CryptoException {
|
|
|
|
Cipher cipher = CryptoInfo.getCipher(TRANSFORMATION);
|
|
try {
|
|
byte[] encryptionKey = getKeys().getEncryptionKey();
|
|
SecretKeySpec spec = new SecretKeySpec(encryptionKey, KEY_ALGORITHM_SPEC);
|
|
|
|
// If no IV is provided, we allow the cipher to provide one.
|
|
if (getIV() == null || getIV().length == 0) {
|
|
cipher.init(Cipher.ENCRYPT_MODE, spec);
|
|
} else {
|
|
cipher.init(Cipher.ENCRYPT_MODE, spec, new IvParameterSpec(getIV()));
|
|
}
|
|
} catch (GeneralSecurityException ex) {
|
|
throw new CryptoException(ex);
|
|
}
|
|
|
|
// Encrypt.
|
|
byte[] encryptedBytes = commonCrypto(cipher, getMessage());
|
|
byte[] iv = cipher.getIV();
|
|
|
|
byte[] hmac;
|
|
// Generate HMAC.
|
|
try {
|
|
hmac = generatedHMACFor(encryptedBytes, keys);
|
|
} catch (NoSuchAlgorithmException e) {
|
|
throw new CryptoException(e);
|
|
} catch (InvalidKeyException e) {
|
|
throw new CryptoException(e);
|
|
}
|
|
|
|
// Update in place. keys is already set.
|
|
this.setHMAC(hmac);
|
|
this.setIV(iv);
|
|
this.setMessage(encryptedBytes);
|
|
}
|
|
|
|
/**
|
|
* Decrypt a CryptoInfo in-place.
|
|
*
|
|
* @throws CryptoException
|
|
*/
|
|
public void decrypt() throws CryptoException {
|
|
|
|
// Check HMAC.
|
|
try {
|
|
if (!generatedHMACIsHMAC()) {
|
|
throw new HMACVerificationException();
|
|
}
|
|
} catch (NoSuchAlgorithmException e) {
|
|
throw new CryptoException(e);
|
|
} catch (InvalidKeyException e) {
|
|
throw new CryptoException(e);
|
|
}
|
|
|
|
Cipher cipher = CryptoInfo.getCipher(TRANSFORMATION);
|
|
try {
|
|
byte[] encryptionKey = getKeys().getEncryptionKey();
|
|
SecretKeySpec spec = new SecretKeySpec(encryptionKey, KEY_ALGORITHM_SPEC);
|
|
cipher.init(Cipher.DECRYPT_MODE, spec, new IvParameterSpec(getIV()));
|
|
} catch (GeneralSecurityException ex) {
|
|
throw new CryptoException(ex);
|
|
}
|
|
byte[] decryptedBytes = commonCrypto(cipher, getMessage());
|
|
byte[] iv = cipher.getIV();
|
|
|
|
// Update in place. keys is already set.
|
|
this.setHMAC(null);
|
|
this.setIV(iv);
|
|
this.setMessage(decryptedBytes);
|
|
}
|
|
|
|
/**
|
|
* Helper to get a Cipher object.
|
|
*
|
|
* @param transformation The type of Cipher to get.
|
|
*/
|
|
private static Cipher getCipher(String transformation) throws CryptoException {
|
|
try {
|
|
return Cipher.getInstance(transformation);
|
|
} catch (NoSuchAlgorithmException e) {
|
|
throw new CryptoException(e);
|
|
} catch (NoSuchPaddingException e) {
|
|
throw new CryptoException(e);
|
|
}
|
|
}
|
|
}
|