package com.facebook.crypto;
import com.facebook.crypto.cipher.NativeGCMCipher;
import com.facebook.crypto.cipher.NativeGCMCipherException;
import com.facebook.crypto.exception.CryptoInitializationException;
import com.facebook.crypto.exception.KeyChainException;
import com.facebook.crypto.keychain.KeyChain;
import com.facebook.crypto.streams.NativeGCMCipherInputStream;
import com.facebook.crypto.streams.NativeGCMCipherOutputStream;
import com.facebook.crypto.util.Assertions;
import com.facebook.crypto.util.NativeCryptoLibrary;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Implements GCM cipher.
* Uses unpadded GCM (128-bits key + 96-bits IV). It includes final AAD.
* This is the default implementation up to Conceal 1.0.6 (Conceal.getCrypto(KeyChain)).
*/
public class CryptoAlgoGcm implements CryptoAlgo {
private final NativeCryptoLibrary mNativeLibrary;
private final KeyChain mKeyChain;
private final CryptoConfig mConfig;
public CryptoAlgoGcm(NativeCryptoLibrary mNativeLibrary, KeyChain mKeyChain, CryptoConfig config) {
this.mNativeLibrary = mNativeLibrary;
this.mKeyChain = mKeyChain;
this.mConfig = config;
}
@Override
public OutputStream wrap(OutputStream cipherStream, Entity entity, byte[] buffer)
throws IOException, CryptoInitializationException, KeyChainException {
cipherStream.write(VersionCodes.CIPHER_SERIALIZATION_VERSION);
cipherStream.write(mConfig.cipherId);
byte[] iv = mKeyChain.getNewIV();
NativeGCMCipher gcmCipher = new NativeGCMCipher(mNativeLibrary);
gcmCipher.encryptInit(mKeyChain.getCipherKey(), iv);
cipherStream.write(iv);
byte[] entityBytes = entity.getBytes();
computeCipherAad(gcmCipher, VersionCodes.CIPHER_SERIALIZATION_VERSION, mConfig.cipherId, entityBytes);
return new NativeGCMCipherOutputStream(cipherStream, gcmCipher, buffer, mConfig.tagLength);
}
@Override
public InputStream wrap(InputStream is, Entity entity)
throws IOException, CryptoInitializationException, KeyChainException {
byte cryptoVersion = (byte) is.read();
byte cipherID = (byte) is.read();
Assertions.checkArgumentForIO(cryptoVersion == VersionCodes.CIPHER_SERIALIZATION_VERSION,
"Unexpected crypto version " + cryptoVersion);
Assertions.checkArgumentForIO(cipherID == mConfig.cipherId,
"Unexpected cipher ID " + cipherID);
byte[] iv = new byte[mConfig.ivLength];
// if iv is not fully read EOFException will be thrown
new DataInputStream(is).readFully(iv);
NativeGCMCipher gcmCipher = new NativeGCMCipher(mNativeLibrary);
gcmCipher.decryptInit(mKeyChain.getCipherKey(), iv);
byte[] entityBytes = entity.getBytes();
computeCipherAad(gcmCipher, cryptoVersion, cipherID, entityBytes);
return new NativeGCMCipherInputStream(is, gcmCipher, mConfig.tagLength);
}
/**
* Computes the Aad data for the cipher.
*/
private void computeCipherAad(NativeGCMCipher gcmCipher, byte cryptoVersion, byte cipherID, byte[] entityBytes)
throws NativeGCMCipherException {
byte[] cryptoVersionBytes = { cryptoVersion };
byte[] cipherIDBytes = { cipherID };
gcmCipher.updateAad(cryptoVersionBytes, 1);
gcmCipher.updateAad(cipherIDBytes, 1);
gcmCipher.updateAad(entityBytes, entityBytes.length);
}
@Override
public int getCipherMetaDataLength() {
return 2 + mConfig.ivLength + mConfig.tagLength;
}
}