/* * 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.cipher; import java.util.Locale; import com.facebook.crypto.CryptoConfig; import com.facebook.crypto.exception.CryptoInitializationException; import com.facebook.crypto.util.Assertions; import com.facebook.crypto.util.NativeCryptoLibrary; import com.facebook.crypto.proguard.annotations.DoNotStrip; /** * Various native functions to encrypt/decrypt data using GCM. */ @DoNotStrip public class NativeGCMCipher { public static final String FAILURE = "Failure"; private static final String CIPHER_ALREADY_INIT = "Cipher has already been initialized"; private static final String CIPHER_NOT_INIT = "Cipher has not been initialized"; private static final String CIPHER_NOT_FINALIZED = "Cipher has not been finalized"; private STATE mCurrentState = STATE.UNINITIALIZED; private final NativeCryptoLibrary mNativeCryptoLibrary; private enum STATE { UNINITIALIZED, ENCRYPT_INITIALIZED, DECRYPT_INITIALIZED, ENCRYPT_FINALIZED, DECRYPT_FINALIZED, }; public NativeGCMCipher(NativeCryptoLibrary nativeCryptoLibrary) { mNativeCryptoLibrary = nativeCryptoLibrary; } public void encryptInit(byte[] key, byte[] iv) throws NativeGCMCipherException, CryptoInitializationException { Assertions.checkState(mCurrentState == STATE.UNINITIALIZED, CIPHER_ALREADY_INIT); mNativeCryptoLibrary.ensureCryptoLoaded(); if (nativeEncryptInit(key, iv) == nativeFailure()) { throw new NativeGCMCipherException("encryptInit"); } mCurrentState = STATE.ENCRYPT_INITIALIZED; } public void decryptInit(byte[] key, byte[] iv) throws NativeGCMCipherException, CryptoInitializationException { Assertions.checkState(mCurrentState == STATE.UNINITIALIZED, CIPHER_ALREADY_INIT); mNativeCryptoLibrary.ensureCryptoLoaded(); if (nativeDecryptInit(key, iv) == nativeFailure()) { throw new NativeGCMCipherException("decryptInit"); } mCurrentState = STATE.DECRYPT_INITIALIZED; } public int update(byte[] data, int offset, int dataLen, byte[] output, int outputOffset) throws NativeGCMCipherException { ensureInInitalizedState(); int bytesRead = nativeUpdate(data, offset, dataLen, output, outputOffset); if (bytesRead < 0) { throw new NativeGCMCipherException( formatStrLocaleSafe( "update: Offset = %d; DataLen = %d; Result = %d", offset, dataLen, bytesRead)); } return bytesRead; } public void updateAad(byte[] data, int dataLength) throws NativeGCMCipherException { ensureInInitalizedState(); if (nativeUpdateAad(data, dataLength) < 0) { throw new NativeGCMCipherException( formatStrLocaleSafe("updateAAd: DataLen = %d", dataLength)); } } public void encryptFinal(byte[] tag, int tagLen) throws NativeGCMCipherException { Assertions.checkState(mCurrentState == STATE.ENCRYPT_INITIALIZED, CIPHER_NOT_INIT); mCurrentState = STATE.ENCRYPT_FINALIZED; if (nativeEncryptFinal(tag, tagLen) == nativeFailure()) { throw new NativeGCMCipherException( formatStrLocaleSafe("encryptFinal: %d", tagLen)); } } public void decryptFinal(byte[] expectedTag, int tagLen) throws NativeGCMCipherException { Assertions.checkState(mCurrentState == STATE.DECRYPT_INITIALIZED, CIPHER_NOT_INIT); mCurrentState = STATE.DECRYPT_FINALIZED; if (nativeDecryptFinal(expectedTag, tagLen) == nativeFailure()) { throw new NativeGCMCipherException( "The message could not be decrypted successfully." + "It has either been tampered with or the wrong resource is being decrypted."); } } public void destroy() throws NativeGCMCipherException { ensureInFinalizedState(); if (nativeDestroy() == nativeFailure()) { throw new NativeGCMCipherException("destroy"); } mCurrentState = STATE.UNINITIALIZED; } public int getCipherBlockSize() { ensureInInitalizedState(); return nativeGetCipherBlockSize(); } private void ensureInInitalizedState() { boolean initialized = mCurrentState == STATE.DECRYPT_INITIALIZED || mCurrentState == STATE.ENCRYPT_INITIALIZED; Assertions.checkState(initialized, CIPHER_NOT_INIT); } private void ensureInFinalizedState() { boolean finalized = mCurrentState == STATE.DECRYPT_FINALIZED || mCurrentState == STATE.ENCRYPT_FINALIZED; Assertions.checkState(finalized, CIPHER_NOT_FINALIZED); } private String formatStrLocaleSafe(String format, Object... args) { return String.format((Locale)null, format, args); } // Used to store the GCM cipher context. @DoNotStrip private long mCtxPtr; // The integer value representing failure in JNI world. private static native int nativeFailure(); private native int nativeEncryptInit(byte[] key, byte[] iv); private native int nativeDecryptInit(byte[] key, byte[] iv); private native int nativeUpdate(byte[] data, int offset, int dataLen, byte[] output, int outputOffset); private native int nativeUpdateAad(byte[] data, int dataLength); private native int nativeEncryptFinal(byte[] tag, int tagLen); private native int nativeDecryptFinal(byte[] tag, int tagLength); private native int nativeDestroy(); private native int nativeGetCipherBlockSize(); }