summaryrefslogtreecommitdiff
path: root/src/main/java/com/google/security/cryptauth/lib/securemessage/CryptoOps.java
diff options
context:
space:
mode:
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.java564
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);
- }
-
-}