diff options
Diffstat (limited to 'src/main/java/com/google/security/cryptauth/lib/securemessage/CryptoOps.java')
-rw-r--r-- | src/main/java/com/google/security/cryptauth/lib/securemessage/CryptoOps.java | 564 |
1 files changed, 0 insertions, 564 deletions
diff --git a/src/main/java/com/google/security/cryptauth/lib/securemessage/CryptoOps.java b/src/main/java/com/google/security/cryptauth/lib/securemessage/CryptoOps.java deleted file mode 100644 index 876bd93..0000000 --- a/src/main/java/com/google/security/cryptauth/lib/securemessage/CryptoOps.java +++ /dev/null @@ -1,564 +0,0 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.google.security.cryptauth.lib.securemessage; - -import com.google.security.annotations.SuppressInsecureCipherModeCheckerReviewed; -import java.io.UnsupportedEncodingException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.Signature; -import java.security.SignatureException; -import javax.annotation.Nullable; -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.Mac; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -/** - * Encapsulates the cryptographic operations used by the {@code SecureMessage*} classes. - */ -public class CryptoOps { - - private CryptoOps() {} // Do not instantiate - - /** - * Enum of supported signature types, with additional mappings to indicate the name of the - * underlying JCA algorithm used to create the signature. - * @see <a href= - * "http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html"> - * Java Cryptography Architecture, Standard Algorithm Name Documentation</a> - */ - public enum SigType { - HMAC_SHA256(SecureMessageProto.SigScheme.HMAC_SHA256, "HmacSHA256", false), - ECDSA_P256_SHA256(SecureMessageProto.SigScheme.ECDSA_P256_SHA256, "SHA256withECDSA", true), - RSA2048_SHA256(SecureMessageProto.SigScheme.RSA2048_SHA256, "SHA256withRSA", true); - - public SecureMessageProto.SigScheme getSigScheme() { - return sigScheme; - } - - public String getJcaName() { - return jcaName; - } - - public boolean isPublicKeyScheme() { - return publicKeyScheme; - } - - public static SigType valueOf(SecureMessageProto.SigScheme sigScheme) { - for (SigType value : values()) { - if (value.sigScheme.equals(sigScheme)) { - return value; - } - } - throw new IllegalArgumentException("Unsupported SigType: " + sigScheme); - } - - private final SecureMessageProto.SigScheme sigScheme; - private final String jcaName; - private final boolean publicKeyScheme; - - SigType(SecureMessageProto.SigScheme sigType, String jcaName, boolean publicKeyScheme) { - this.sigScheme = sigType; - this.jcaName = jcaName; - this.publicKeyScheme = publicKeyScheme; - } - } - - /** - * Enum of supported encryption types, with additional mappings to indicate the name of the - * underlying JCA algorithm used to perform the encryption. - * @see <a href= - * "http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html"> - * Java Cryptography Architecture, Standard Algorithm Name Documentation</a> - */ - public enum EncType { - NONE(SecureMessageProto.EncScheme.NONE, "InvalidDoNotUseForJCA"), - AES_256_CBC(SecureMessageProto.EncScheme.AES_256_CBC, "AES/CBC/PKCS5Padding"); - - public SecureMessageProto.EncScheme getEncScheme() { - return encScheme; - } - - public String getJcaName() { - return jcaName; - } - - public static EncType valueOf(SecureMessageProto.EncScheme encScheme) { - for (EncType value : values()) { - if (value.encScheme.equals(encScheme)) { - return value; - } - } - throw new IllegalArgumentException("Unsupported EncType: " + encScheme); - } - - private final SecureMessageProto.EncScheme encScheme; - private final String jcaName; - - EncType(SecureMessageProto.EncScheme encScheme, String jcaName) { - this.encScheme = encScheme; - this.jcaName = jcaName; - } - } - - /** - * Truncated hash output length, in bytes. - */ - static final int DIGEST_LENGTH = 20; - /** - * A salt value specific to this library, generated as SHA-256("SecureMessage") - */ - private static final byte[] SALT = sha256("SecureMessage"); - - /** - * Signs {@code data} using the algorithm specified by {@code sigType} with {@code signingKey}. - * - * @param rng is required for public key signature schemes - * @return raw signature - * @throws InvalidKeyException if {@code signingKey} is incompatible with {@code sigType} - * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code sigType} - */ - static byte[] sign( - SigType sigType, Key signingKey, @Nullable SecureRandom rng, byte[] data) - throws InvalidKeyException, NoSuchAlgorithmException { - if ((signingKey == null) || (data == null)) { - throw new NullPointerException(); - } - if (sigType.isPublicKeyScheme()) { - if (rng == null) { - throw new NullPointerException(); - } - if (!(signingKey instanceof PrivateKey)) { - throw new InvalidKeyException("Expected a PrivateKey"); - } - Signature sigScheme = Signature.getInstance(sigType.getJcaName()); - sigScheme.initSign((PrivateKey) signingKey, rng); - try { - // We include a fixed magic value (salt) in the signature so that if the signing key is - // reused in another context we can't be confused -- provided that the other user of the - // signing key only signs statements that do not begin with this salt. - sigScheme.update(SALT); - sigScheme.update(data); - return sigScheme.sign(); - } catch (SignatureException e) { - throw new IllegalStateException(e); // Consistent with failures in Mac.doFinal - } - } else { - Mac macScheme = Mac.getInstance(sigType.getJcaName()); - // Note that an AES-256 SecretKey should work with most Mac schemes - SecretKey derivedKey = deriveAes256KeyFor(getSecretKey(signingKey), getPurpose(sigType)); - macScheme.init(derivedKey); - return macScheme.doFinal(data); - } - } - - /** - * Verifies the {@code signature} on {@code data} using the algorithm specified by - * {@code sigType} with {@code verificationKey}. - * - * @return true iff the signature is verified - * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code sigType} - * @throws InvalidKeyException if {@code verificationKey} is incompatible with {@code sigType} - * @throws SignatureException - */ - static boolean verify(Key verificationKey, SigType sigType, byte[] signature, byte[] data) - throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - if ((verificationKey == null) || (signature == null) || (data == null)) { - throw new NullPointerException(); - } - if (sigType.isPublicKeyScheme()) { - if (!(verificationKey instanceof PublicKey)) { - throw new InvalidKeyException("Expected a PublicKey"); - } - Signature sigScheme = Signature.getInstance(sigType.getJcaName()); - sigScheme.initVerify((PublicKey) verificationKey); - sigScheme.update(SALT); // See the comments in sign() for more on this - sigScheme.update(data); - return sigScheme.verify(signature); - } else { - Mac macScheme = Mac.getInstance(sigType.getJcaName()); - SecretKey derivedKey = - deriveAes256KeyFor(getSecretKey(verificationKey), getPurpose(sigType)); - macScheme.init(derivedKey); - return constantTimeArrayEquals(signature, macScheme.doFinal(data)); - } - } - - /** - * Generate a random IV appropriate for use with the algorithm specified in {@code encType}. - * - * @return a freshly generated IV (a random byte sequence of appropriate length) - * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code encType} - */ - @SuppressInsecureCipherModeCheckerReviewed - // See b/26525455 for security review. - static byte[] generateIv(EncType encType, SecureRandom rng) throws NoSuchAlgorithmException { - if (rng == null) { - throw new NullPointerException(); - } - try { - Cipher encrypter = Cipher.getInstance(encType.getJcaName()); - byte[] iv = new byte[encrypter.getBlockSize()]; - rng.nextBytes(iv); - return iv; - } catch (NoSuchPaddingException e) { - throw new NoSuchAlgorithmException(e); // Consolidate into NoSuchAlgorithmException - } - } - - /** - * Encrypts {@code plaintext} using the algorithm specified in {@code encType}, with the specified - * {@code iv} and {@code encryptionKey}. - * - * @param rng source of randomness to be used with the specified cipher, if necessary - * @return encrypted data - * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code encType} - * @throws InvalidKeyException if {@code encryptionKey} is incompatible with {@code encType} - */ - @SuppressInsecureCipherModeCheckerReviewed - // See b/26525455 for security review. - static byte[] encrypt( - Key encryptionKey, EncType encType, @Nullable SecureRandom rng, byte[] iv, byte[] plaintext) - throws NoSuchAlgorithmException, InvalidKeyException { - if ((encryptionKey == null) || (iv == null) || (plaintext == null)) { - throw new NullPointerException(); - } - if (encType == EncType.NONE) { - throw new NoSuchAlgorithmException("Cannot use NONE type here"); - } - try { - Cipher encrypter = Cipher.getInstance(encType.getJcaName()); - SecretKey derivedKey = - deriveAes256KeyFor(getSecretKey(encryptionKey), getPurpose(encType)); - encrypter.init(Cipher.ENCRYPT_MODE, derivedKey, new IvParameterSpec(iv), rng); - return encrypter.doFinal(plaintext); - } catch (InvalidAlgorithmParameterException e) { - throw new AssertionError(e); // Should never happen - } catch (IllegalBlockSizeException e) { - throw new AssertionError(e); // Should never happen - } catch (BadPaddingException e) { - throw new AssertionError(e); // Should never happen - } catch (NoSuchPaddingException e) { - throw new NoSuchAlgorithmException(e); // Consolidate into NoSuchAlgorithmException - } - } - - /** - * Decrypts {@code ciphertext} using the algorithm specified in {@code encType}, with the - * specified {@code iv} and {@code decryptionKey}. - * - * @return the plaintext (decrypted) data - * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code encType} - * @throws InvalidKeyException if {@code decryptionKey} is incompatible with {@code encType} - * @throws InvalidAlgorithmParameterException if {@code encType} exceeds legal cryptographic - * strength limits in this jurisdiction - * @throws IllegalBlockSizeException if {@code ciphertext} contains an illegal block - * @throws BadPaddingException if {@code ciphertext} contains an illegal padding - */ - @SuppressInsecureCipherModeCheckerReviewed - // See b/26525455 for security review - static byte[] decrypt(Key decryptionKey, EncType encType, byte[] iv, byte[] ciphertext) - throws NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException, - IllegalBlockSizeException, BadPaddingException { - if ((decryptionKey == null) || (iv == null) || (ciphertext == null)) { - throw new NullPointerException(); - } - if (encType == EncType.NONE) { - throw new NoSuchAlgorithmException("Cannot use NONE type here"); - } - try { - Cipher decrypter = Cipher.getInstance(encType.getJcaName()); - SecretKey derivedKey = - deriveAes256KeyFor(getSecretKey(decryptionKey), getPurpose(encType)); - decrypter.init(Cipher.DECRYPT_MODE, derivedKey, new IvParameterSpec(iv)); - return decrypter.doFinal(ciphertext); - } catch (NoSuchPaddingException e) { - throw new AssertionError(e); // Should never happen - } - } - - /** - * Computes a collision-resistant hash of {@link #DIGEST_LENGTH} bytes - * (using a truncated SHA-256 output). - */ - static byte[] digest(byte[] data) throws NoSuchAlgorithmException { - MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); - byte[] truncatedHash = new byte[DIGEST_LENGTH]; - System.arraycopy(sha256.digest(data), 0, truncatedHash, 0, DIGEST_LENGTH); - return truncatedHash; - } - - /** - * Returns {@code true} if the two arrays are equal to one another. - * When the two arrays differ in length, trivially returns {@code false}. - * When the two arrays are equal in length, does a constant-time comparison - * of the two, i.e. does not abort the comparison when the first differing - * element is found. - * - * <p>NOTE: This is a copy of {@code java/com/google/math/crypto/ConstantTime#arrayEquals}. - * - * @param a An array to compare - * @param b Another array to compare - * @return {@code true} if these arrays are both null or if they have equal - * length and equal bytes in all elements - */ - static boolean constantTimeArrayEquals(@Nullable byte[] a, @Nullable byte[] b) { - if (a == null || b == null) { - return (a == b); - } - if (a.length != b.length) { - return false; - } - byte result = 0; - for (int i = 0; i < b.length; i++) { - result = (byte) (result | a[i] ^ b[i]); - } - return (result == 0); - } - - // @VisibleForTesting - static String getPurpose(SigType sigType) { - return "SIG:" + sigType.getSigScheme().getNumber(); - } - - // @VisibleForTesting - static String getPurpose(EncType encType) { - return "ENC:" + encType.getEncScheme().getNumber(); - } - - private static SecretKey getSecretKey(Key key) throws InvalidKeyException { - if (!(key instanceof SecretKey)) { - throw new InvalidKeyException("Expected a SecretKey"); - } - return (SecretKey) key; - } - - /** - * @return the UTF-8 encoding of the given string - * @throws RuntimeException if the UTF-8 charset is not present. - */ - public static byte[] utf8StringToBytes(String input) { - try { - return input.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); // Shouldn't happen, UTF-8 is universal - } - } - - /** - * @return SHA-256(UTF-8 encoded input) - */ - public static byte[] sha256(String input) { - MessageDigest sha256; - try { - sha256 = MessageDigest.getInstance("SHA-256"); - return sha256.digest(utf8StringToBytes(input)); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("No security provider initialized yet?", e); - } - } - - /** - * A key derivation function specific to this library, which accepts a {@code masterKey} and an - * arbitrary {@code purpose} describing the intended application of the derived sub-key, - * and produces a derived AES-256 key safe to use as if it were independent of any other - * derived key which used a different {@code purpose}. - * - * @param masterKey any key suitable for use with HmacSHA256 - * @param purpose a UTF-8 encoded string describing the intended purpose of derived key - * @return a derived SecretKey suitable for use with AES-256 - * @throws InvalidKeyException if the encoded form of {@code masterKey} cannot be accessed - */ - static SecretKey deriveAes256KeyFor(SecretKey masterKey, String purpose) - throws NoSuchAlgorithmException, InvalidKeyException { - return new SecretKeySpec(hkdf(masterKey, SALT, utf8StringToBytes(purpose)), "AES"); - } - - /** - * Implements HKDF (RFC 5869) with the SHA-256 hash and a 256-bit output key length. - * - * Please make sure to select a salt that is fixed and unique for your codebase, and use the - * {@code info} parameter to specify any additional bits that should influence the derived key. - * - * @param inputKeyMaterial master key from which to derive sub-keys - * @param salt a (public) randomly generated 256-bit input that can be re-used - * @param info arbitrary information that is bound to the derived key (i.e., used in its creation) - * @return raw derived key bytes = HKDF-SHA256(inputKeyMaterial, salt, info) - * @throws InvalidKeyException if the encoded form of {@code inputKeyMaterial} cannot be accessed - */ - public static byte[] hkdf(SecretKey inputKeyMaterial, byte[] salt, byte[] info) - throws NoSuchAlgorithmException, InvalidKeyException { - return hkdf(inputKeyMaterial, salt, info, /* length= */ 32); - } - - /** - * Implements HKDF (RFC 5869) with the SHA-256 hash. - * - * <p>Please make sure to select a salt that is fixed and unique for your codebase, and use the - * {@code info} parameter to specify any additional bits that should influence the derived key. - * - * @param inputKeyMaterial master key from which to derive sub-keys - * @param salt a (public) randomly generated 256-bit input that can be re-used - * @param info arbitrary information that is bound to the derived key (i.e., used in its creation) - * @param length length of returned key material - * @return raw derived key bytes = HKDF-SHA256(inputKeyMaterial, salt, info) - * @throws InvalidKeyException if the encoded form of {@code inputKeyMaterial} cannot be accessed - */ - public static byte[] hkdf(SecretKey inputKeyMaterial, byte[] salt, byte[] info, int length) - throws NoSuchAlgorithmException, InvalidKeyException { - if ((inputKeyMaterial == null) || (salt == null) || (info == null)) { - throw new NullPointerException(); - } - if (length < 0) { - throw new IllegalArgumentException("Length must be positive"); - } - return hkdfSha256Expand(hkdfSha256Extract(inputKeyMaterial, salt), info, length); - } - - /** - * @return the concatenation of {@code a} and {@code b}, treating {@code null} as the empty array. - */ - static byte[] concat(@Nullable byte[] a, @Nullable byte[] b) { - if ((a == null) && (b == null)) { - return new byte[] { }; - } - if (a == null) { - return b; - } - if (b == null) { - return a; - } - byte[] result = new byte[a.length + b.length]; - System.arraycopy(a, 0, result, 0, a.length); - System.arraycopy(b, 0, result, a.length, b.length); - return result; - } - - /** - * Since {@code Arrays.copyOfRange(...)} is not available on older Android platforms, - * a custom method for computing a subarray is provided here. - * - * @return the substring of {@code in} from {@code beginIndex} (inclusive) - * up to {@code endIndex} (exclusive) - */ - static byte[] subarray(byte[] in, int beginIndex, int endIndex) { - if (in == null) { - throw new NullPointerException(); - } - int length = endIndex - beginIndex; - if ((length < 0) - || (beginIndex < 0) - || (endIndex < 0) - || (beginIndex >= in.length) - || (endIndex > in.length)) { - throw new IndexOutOfBoundsException(); - } - byte[] result = new byte[length]; - if (length > 0) { - System.arraycopy(in, beginIndex, result, 0, length); - } - return result; - } - - /** - * The HKDF (RFC 5869) extraction function, using the SHA-256 hash function. This function is used - * to pre-process the inputKeyMaterial and mix it with the salt, producing output suitable for use - * with HKDF expansion function (which produces the actual derived key). - * - * @see #hkdfSha256Expand(byte[], byte[], int) - * @return HMAC-SHA256(salt, inputKeyMaterial) (salt is the "key" for the HMAC) - * @throws InvalidKeyException if the encoded form of {@code inputKeyMaterial} cannot be accessed - * @throws NoSuchAlgorithmException if the HmacSHA256 or AES algorithms are unavailable - */ - private static byte[] hkdfSha256Extract(SecretKey inputKeyMaterial, byte[] salt) - throws NoSuchAlgorithmException, InvalidKeyException { - Mac macScheme = Mac.getInstance("HmacSHA256"); - try { - macScheme.init(new SecretKeySpec(salt, "AES")); - } catch (InvalidKeyException e) { - throw new AssertionError(e); // This should never happen - } - // Note that the SecretKey encoding format is defined to be RAW, so the encoded form should be - // consistent across implementations. - byte[] encodedKeyMaterial = inputKeyMaterial.getEncoded(); - if (encodedKeyMaterial == null) { - throw new InvalidKeyException("Cannot get encoded form of SecretKey"); - } - return macScheme.doFinal(encodedKeyMaterial); - } - - /** - * HKDF (RFC 5869) expansion function, using the SHA-256 hash function. - * - * @param pseudoRandomKey should be generated by {@link #hkdfSha256Extract(SecretKey, byte[])} - * @param info arbitrary information the derived key should be bound to - * @param length length of the output key material in bytes - * @return raw derived key bytes = HMAC-SHA256(pseudoRandomKey, info | 0x01) - * @throws NoSuchAlgorithmException if the HmacSHA256 or AES algorithms are unavailable - */ - private static byte[] hkdfSha256Expand(byte[] pseudoRandomKey, byte[] info, int length) - throws NoSuchAlgorithmException { - Mac macScheme = Mac.getInstance("HmacSHA256"); - try { - macScheme.init(new SecretKeySpec(pseudoRandomKey, "AES")); - } catch (InvalidKeyException e) { - throw new AssertionError(e); // This should never happen - } - - // Number of blocks N = ceil(hash length / output length). - int blocks = length / 32; - if (length % 32 > 0) { - blocks += 1; - } - - // The counter used to generate the blocks according to the RFC is only one byte long, - // which puts a limit on the number of blocks possible. - if (blocks > 0xFF) { - throw new IllegalArgumentException("Maximum HKDF output length exceeded."); - } - - byte[] outputBlock = new byte[32]; - byte[] counter = new byte[1]; - byte[] output = new byte[32 * blocks]; - for (int i = 0; i < blocks; ++i) { - macScheme.reset(); - if (i > 0) { - // Previous block - macScheme.update(outputBlock); - } - // Arbitrary info - macScheme.update(info); - // Counter - counter[0] = (byte) (i + 1); - outputBlock = macScheme.doFinal(counter); - - System.arraycopy(outputBlock, 0, output, 32 * i, 32); - } - - return subarray(output, 0, length); - } - -} |