/*
* *
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* /
*/
package org.anhonesteffort.flock.crypto;
import org.anhonesteffort.flock.util.Base64;
import org.anhonesteffort.flock.util.Util;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
/**
* Programmer: rhodey
*/
public class MasterCipher {
public static final byte CURRENT_CIPHER_VERSION = 0x01;
protected static final int MAC_LENGTH_BYTES = 32;
protected static final int IV_LENGTH_BYTES = 16;
private final SecretKey cipherKey;
private final SecretKey macKey;
protected MasterCipher(SecretKey cipherKey, SecretKey macKey) {
this.cipherKey = cipherKey;
this.macKey = macKey;
}
public byte[] encryptAndEncode(byte[] data)
throws IOException, GeneralSecurityException
{
Cipher encryptingCipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
encryptingCipher.init(Cipher.ENCRYPT_MODE, cipherKey);
Mac hmac = Mac.getInstance("HmacSHA256");
hmac.init(macKey);
byte[] iv = encryptingCipher.getIV();
byte[] ciphertext = encryptingCipher.doFinal(data);
byte[] mac = hmac.doFinal(Util.combine(new byte[] {CURRENT_CIPHER_VERSION}, iv, ciphertext));
return Base64.encodeBytesToBytes(Util.combine(new byte[] {CURRENT_CIPHER_VERSION}, iv, ciphertext, mac));
}
public String encryptAndEncode(String data)
throws IOException, GeneralSecurityException
{
return new String(encryptAndEncode(data.getBytes()));
}
public byte[] decodeAndDecrypt(byte[] encodedVersionIvCiphertextAndMac)
throws InvalidMacException, IOException, GeneralSecurityException
{
byte[] versionIvCiphertextAndMac = Base64.decode(encodedVersionIvCiphertextAndMac);
if (versionIvCiphertextAndMac.length <= (1 + IV_LENGTH_BYTES + MAC_LENGTH_BYTES))
throw new GeneralSecurityException("invalid length on decoded cipherVersion, iv, ciphertext and mac");
byte version = versionIvCiphertextAndMac[0];
if (version != CURRENT_CIPHER_VERSION)
throw new InvalidCipherVersionException("invalid cipher cipherVersion >> " + version);
byte[] iv = Arrays.copyOfRange(versionIvCiphertextAndMac, 1, 1 + IV_LENGTH_BYTES);
byte[] ciphertext = Arrays.copyOfRange(versionIvCiphertextAndMac,
1 + IV_LENGTH_BYTES,
versionIvCiphertextAndMac.length - MAC_LENGTH_BYTES);
byte[] mac = Arrays.copyOfRange(versionIvCiphertextAndMac,
versionIvCiphertextAndMac.length - MAC_LENGTH_BYTES,
versionIvCiphertextAndMac.length);
Cipher decryptingCipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Mac hmac = Mac.getInstance("HmacSHA256");
decryptingCipher.init(Cipher.DECRYPT_MODE, cipherKey, ivSpec);
hmac.init(macKey);
verifyMac(hmac, Util.combine(new byte[]{version}, iv, ciphertext), mac);
return decryptingCipher.doFinal(ciphertext);
}
public String decodeAndDecrypt(String data)
throws InvalidMacException, IOException, GeneralSecurityException
{
return new String(decodeAndDecrypt(data.getBytes()));
}
protected static void verifyMac(Mac hmac, byte[] theirData, byte[] theirMac)
throws InvalidMacException
{
byte[] ourMac = hmac.doFinal(theirData);
if (!MessageDigest.isEqual(theirMac, ourMac))
throw new InvalidMacException("INVALID MAC");
}
}