/* * Copyright (c) 2014, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ package com.facebook.crypto; import com.facebook.crypto.exception.CryptoInitializationException; import com.facebook.crypto.exception.KeyChainException; import com.facebook.crypto.keychain.KeyChain; import com.facebook.crypto.mac.NativeMac; import com.facebook.crypto.streams.FixedSizeByteArrayOutputStream; import com.facebook.crypto.streams.NativeMacLayeredInputStream; import com.facebook.crypto.streams.NativeMacLayeredOutputStream; import com.facebook.crypto.util.Assertions; import com.facebook.crypto.util.NativeCryptoLibrary; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class Crypto { private final KeyChain mKeyChain; private final NativeCryptoLibrary mNativeCryptoLibrary; private final CryptoAlgo mCryptoAlgo; /** * @deprecated Use ConcealAndroid.get().createCrypto(...) */ @Deprecated public Crypto(KeyChain keyChain, NativeCryptoLibrary nativeCryptoLibrary) { this(keyChain, nativeCryptoLibrary, CryptoConfig.KEY_128); } public Crypto(KeyChain keyChain, NativeCryptoLibrary nativeCryptoLibrary, CryptoConfig config) { mKeyChain = new CheckedKeyChain(keyChain, config); mNativeCryptoLibrary = nativeCryptoLibrary; mCryptoAlgo = new CryptoAlgoGcm(mNativeCryptoLibrary, mKeyChain, config); } /** * Tells if crypto native library and this class can be used. * @return true if and only if libraries could be loaded successfully. */ public boolean isAvailable() { try { mNativeCryptoLibrary.ensureCryptoLoaded(); return true; } catch (Throwable t) { return false; } } /** * Invokes getCipherOutputStream(cipherStream, entity, null) */ public OutputStream getCipherOutputStream(OutputStream cipherStream, Entity entity) throws IOException, CryptoInitializationException, KeyChainException { return getCipherOutputStream(cipherStream, entity, null); } /** * Gives you an output stream wrapper that encrypts the text written. * * @param cipherStream The stream that the encrypted data will be written to. * @param entity A unique object identifying what is being written. * @param encryptBuffer an auxiliar buffer used to encrypt the content * if null a new one will be created (size: 256+tagSize) * * @return A ciphered output stream to write to. * @throws IOException */ public OutputStream getCipherOutputStream(OutputStream cipherStream, Entity entity, byte[] encryptBuffer) throws IOException, CryptoInitializationException, KeyChainException { return mCryptoAlgo.wrap(cipherStream, entity, encryptBuffer); } /** * Gives you an input stream wrapper that decrypts another stream. * You must read the whole stream to completion, i.e. till -1. Failure * to do so may result in a security vulnerability. * * @param cipherStream The stream from which the encrypted data is read. * @param entity A unique object identifying what is being read. * * @return A ciphered input stream to read from. * @throws IOException * @throws CryptoInitializationException Thrown if the crypto libraries could not be initialized. * @throws KeyChainException Thrown if there is trouble managing keys. */ public InputStream getCipherInputStream(InputStream cipherStream, Entity entity) throws IOException, CryptoInitializationException, KeyChainException { return mCryptoAlgo.wrap(cipherStream, entity); } /** * A convenience method to encrypt data if the data to be processed is small and can * be held in memory. * @param plainTextBytes Bytes of the plain text. * @param entity Entity to process. * @return cipherText. * @throws KeyChainException * @throws CryptoInitializationException * @throws IOException * @throws CryptoInitializationException Thrown if the crypto libraries could not be initialized. * @throws KeyChainException Thrown if there is trouble managing keys. */ public byte[] encrypt(byte[] plainTextBytes, Entity entity) throws KeyChainException, CryptoInitializationException, IOException { int cipheredBytesLength = plainTextBytes.length + getCipherMetaDataLength(); FixedSizeByteArrayOutputStream outputStream = new FixedSizeByteArrayOutputStream(cipheredBytesLength); OutputStream cipherStream = getCipherOutputStream(outputStream, entity, null); cipherStream.write(plainTextBytes); cipherStream.close(); return outputStream.getBytes(); } /** * A convenience method to decrypt data if the data to be processed is small and can * be held in memory. * @param cipherTextBytes Bytes of the cipher text. * @param entity Entity to process. * @return cipherText. * @throws IOException * @throws CryptoInitializationException Thrown if the crypto libraries could not be initialized. * @throws KeyChainException Thrown if there is trouble managing keys. */ public byte[] decrypt(byte[] cipherTextBytes, Entity entity) throws KeyChainException, CryptoInitializationException, IOException { int cipherTextLength = cipherTextBytes.length; ByteArrayInputStream cipheredStream = new ByteArrayInputStream(cipherTextBytes); InputStream plainTextStream = getCipherInputStream(cipheredStream, entity); int plainTextLength = cipherTextLength - getCipherMetaDataLength(); FixedSizeByteArrayOutputStream output = new FixedSizeByteArrayOutputStream(plainTextLength); byte[] buffer = new byte[1024]; int read; while ((read = plainTextStream.read(buffer)) != -1) { output.write(buffer, 0, read); } plainTextStream.close(); return output.getBytes(); } /** * Gives you an output stream wrapper that adds some data to the stream which * can be used to ensure its integrity. * * @param stream The stream to which the data will be written * @param entity A unique object identifying what is being written. * * @return A ciphered input stream to read from. * @throws IOException * @throws CryptoInitializationException Thrown if the crypto libraries could not be initialized. * @throws KeyChainException Thrown if there is trouble managing keys. */ public OutputStream getMacOutputStream(OutputStream stream, Entity entity) throws IOException, KeyChainException, CryptoInitializationException { stream.write(VersionCodes.MAC_SERIALIZATION_VERSION); stream.write(VersionCodes.MAC_ID); NativeMac nativeMac = new NativeMac(mNativeCryptoLibrary); byte[] macKey = mKeyChain.getMacKey(); nativeMac.init(macKey, macKey.length); byte[] entityBytes = entity.getBytes(); computeMacAad(nativeMac, VersionCodes.MAC_SERIALIZATION_VERSION, VersionCodes.MAC_ID, entityBytes); return new NativeMacLayeredOutputStream(nativeMac, stream); } /** * Gives you an input stream wrapper that ensures the integrity of another * stream. You must read the whole stream to completion, i.e. till -1. Failure * to do so may result in a security vulnerability. * * @param stream The stream from which the data is read. * @param entity A unique object identifying what is being read. * * @return A ciphered input stream to read from. * @throws IOException * @throws CryptoInitializationException Thrown if the crypto libraries could not be initialized. * @throws KeyChainException Thrown if there is trouble managing keys. */ public InputStream getMacInputStream(InputStream stream, Entity entity) throws IOException, KeyChainException, CryptoInitializationException { byte macVersion = (byte) stream.read(); Assertions.checkArgumentForIO(macVersion == VersionCodes.MAC_SERIALIZATION_VERSION, "Unexpected mac version " + macVersion); byte macID = (byte) stream.read(); Assertions.checkArgumentForIO(macID == VersionCodes.MAC_ID, "Unexpected mac ID " + macID); NativeMac nativeMac = new NativeMac(mNativeCryptoLibrary); byte[] macKey = mKeyChain.getMacKey(); nativeMac.init(macKey, macKey.length); byte[] entityBytes = entity.getBytes(); computeMacAad(nativeMac, macVersion, VersionCodes.MAC_ID, entityBytes); return new NativeMacLayeredInputStream(nativeMac, stream); } /** * Computes the authenticated data for the mac. */ private static void computeMacAad(NativeMac mac, byte macVersion, byte macID, byte[] entityBytes) throws IOException { byte[] cryptoVersionBytes = { macVersion }; byte[] macIDBytes = { macID }; mac.update(cryptoVersionBytes, 0, 1); mac.update(macIDBytes, 0, 1); mac.update(entityBytes, 0, entityBytes.length); } /** * Gets the length of the meta data for the version of the API being decrypted. * This should preserve the following invariant: * </p> * Ciphertext data size = Plaintext data + Cipher meta data. */ /* package protected */ int getCipherMetaDataLength() { return mCryptoAlgo.getCipherMetaDataLength(); } }