All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hedera.hashgraph.sdk.Crypto Maven / Gradle / Ivy

There is a newer version: 2.40.0
Show newest version
/*-
 *
 * Hedera Java SDK
 *
 * Copyright (C) 2020 - 2024 Hedera Hashgraph, 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
 *
 *      http://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.hedera.hashgraph.sdk;

import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.x9.X9IntegerConverter;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.digests.SHA384Digest;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.jcajce.provider.digest.Keccak;
import org.bouncycastle.math.ec.ECAlgorithms;
import org.bouncycastle.math.ec.ECPoint;

import javax.annotation.Nullable;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

/**
 * Utility class used internally by the sdk.
 */
final class Crypto {
    static final int IV_LEN = 16;
    static final int ITERATIONS = 262144;
    static final int SALT_LEN = 32;
    static final int DK_LEN = 32;

    // OpenSSL doesn't like longer derived keys
    static final int CBC_DK_LEN = 16;

    static final X9ECParameters ECDSA_SECP256K1_CURVE = SECNamedCurves.getByName("secp256k1");
    static final ECDomainParameters ECDSA_SECP256K1_DOMAIN = new ECDomainParameters(
        ECDSA_SECP256K1_CURVE.getCurve(),
        ECDSA_SECP256K1_CURVE.getG(),
        ECDSA_SECP256K1_CURVE.getN(),
        ECDSA_SECP256K1_CURVE.getH());

    /**
     * Constructor.
     */
    private Crypto() {
    }

    /**
     * Derive a sha 256 key.
     *
     * @param passphrase                the password will be converted into bytes
     * @param salt                      the salt to be mixed in
     * @param iterations                the iterations for mixing
     * @param dkLenBytes                the key length in bytes
     * @return                          the key parameter object
     */
    static KeyParameter deriveKeySha256(String passphrase, byte[] salt, int iterations, int dkLenBytes) {
        PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA256Digest());
        gen.init(passphrase.getBytes(StandardCharsets.UTF_8), salt, iterations);

        return (KeyParameter) gen.generateDerivedParameters(dkLenBytes * 8);
    }

    /**
     * Initialize an advanced encryption standard counter mode cipher.
     *
     * @param cipherKey                 the cipher key
     * @param iv                        the initialization vector byte array
     * @param forDecrypt                is this for decryption
     * @return                          the aes ctr cipher
     */
    static Cipher initAesCtr128(KeyParameter cipherKey, byte[] iv, boolean forDecrypt) {
        Cipher aesCipher;

        try {
            aesCipher = Cipher.getInstance("AES/CTR/NOPADDING");
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new Error("platform does not support AES-CTR ciphers", e);
        }

        return initAesCipher(aesCipher, cipherKey, iv, forDecrypt);
    }

    /**
     * Initialize an advanced encryption standard cipher block chaining mode
     * cipher for encryption.
     *
     * @param cipherKey                 the cipher key
     * @param iv                        the initialization vector byte array
     * @return                          the aes cbc cipher
     */
    static Cipher initAesCbc128Encrypt(KeyParameter cipherKey, byte[] iv) {
        Cipher aesCipher;

        try {
            aesCipher = Cipher.getInstance("AES/CBC/NoPadding");
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new Error("platform does not support AES-CBC ciphers", e);
        }

        return initAesCipher(aesCipher, cipherKey, iv, false);
    }

    /**
     * Initialize an advanced encryption standard cipher block chaining mode
     * cipher for decryption.
     *
     * @param cipherKey                 the cipher key
     * @param parameters                the algorithm parameters
     * @return                          the aes cbc cipher
     */
    static Cipher initAesCbc128Decrypt(KeyParameter cipherKey, AlgorithmParameters parameters) {
        Cipher aesCipher;

        try {
            aesCipher = Cipher.getInstance("AES/CBC/NoPadding");
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new Error("platform does not support AES-CBC ciphers", e);
        }

        try {
            aesCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(cipherKey.getKey(), 0, 16, "AES"), parameters);
        } catch (InvalidKeyException e) {
            throw new Error("platform does not support AES-128 ciphers", e);
        } catch (InvalidAlgorithmParameterException e) {
            throw new Error(e);
        }

        return aesCipher;
    }

    /**
     * Create a new aes cipher.
     *
     * @param aesCipher                 the aes cipher
     * @param cipherKey                 the cipher key
     * @param iv                        the initialization vector byte array
     * @param forDecrypt                is this for decryption True or encryption False
     * @return                          the new aes cipher
     */
    private static Cipher initAesCipher(Cipher aesCipher, KeyParameter cipherKey, byte[] iv, boolean forDecrypt) {
        int mode = forDecrypt ? Cipher.DECRYPT_MODE : Cipher.ENCRYPT_MODE;

        try {
            aesCipher.init(mode, new SecretKeySpec(cipherKey.getKey(), 0, 16, "AES"),
                new IvParameterSpec(iv));
        } catch (InvalidKeyException e) {
            throw new Error("platform does not support AES-128 ciphers", e);
        } catch (InvalidAlgorithmParameterException e) {
            throw new Error(e);
        }

        return aesCipher;
    }

    /**
     * Encrypt a byte array with the aes ctr cipher.
     *
     * @param cipherKey                 the cipher key
     * @param iv                        the initialization vector
     * @param input                     the byte array to encrypt
     * @return                          the encrypted byte array
     */
    static byte[] encryptAesCtr128(KeyParameter cipherKey, byte[] iv, byte[] input) {
        Cipher aesCipher = initAesCtr128(cipherKey, iv, false);
        return runCipher(aesCipher, input);
    }

    /**
     * Decrypt a byte array with the aes ctr cipher.
     *
     * @param cipherKey                 the cipher key
     * @param iv                        the initialization vector
     * @param input                     the byte array to decrypt
     * @return                          the decrypted byte array
     */
    static byte[] decryptAesCtr128(KeyParameter cipherKey, byte[] iv, byte[] input) {
        Cipher aesCipher = initAesCtr128(cipherKey, iv, true);
        return runCipher(aesCipher, input);
    }

    /**
     * Run the cipher on the given input.
     *
     * @param cipher                    the cipher
     * @param input                     the byte array
     * @return                          the output of running the cipher
     */
    static byte[] runCipher(Cipher cipher, byte[] input) {
        byte[] output = new byte[cipher.getOutputSize(input.length)];

        try {
            cipher.doFinal(input, 0, input.length, output);
        } catch (ShortBufferException | IllegalBlockSizeException | BadPaddingException e) {
            throw new Error(e);
        }

        return output;
    }

    /**
     * Calculate a hash message authentication code using the secure hash
     * algorithm variant 384.
     *
     * @param cipherKey                 the cipher key
     * @param iv                        the initialization vector
     * @param input                     the byte array
     * @return                          the hmac using sha 384
     */
    static byte[] calcHmacSha384(KeyParameter cipherKey, @Nullable byte[] iv, byte[] input) {
        HMac hmacSha384 = new HMac(new SHA384Digest());
        byte[] output = new byte[hmacSha384.getMacSize()];

        hmacSha384.init(new KeyParameter(cipherKey.getKey(), 16, 16));
        if (iv != null) {
            hmacSha384.update(iv, 0, iv.length);
        }
        hmacSha384.update(input, 0, input.length);
        hmacSha384.doFinal(output, 0);

        return output;
    }

    /**
     * Calculate a keccak 256-bit hash.
     *
     * @param message                   the message to be hashed
     * @return                          the hash
     */
    static byte[] calcKeccak256(byte[] message) {
        var digest = new Keccak.Digest256();
        digest.update(message);
        return digest.digest();
    }

    /**
     * Generate some randomness.
     *
     * @param len                       the number of bytes requested
     * @return                          the byte array of randomness
     */
    static byte[] randomBytes(int len) {
        byte[] out = new byte[len];
        ThreadLocalSecureRandom.current().nextBytes(out);

        return out;
    }

    /**
     * Given the r and s components of a signature and the hash value of the message, recover and return the public key
     * according to the algorithm in SEC1v2 section 4.1.6.
     * 

* Calculations and explanations in this method were taken and adapted from * incubator-tuweni lib * * @param recId Which possible key to recover. * @param r The R component of the signature. * @param s The S component of the signature. * @param messageHash Hash of the data that was signed. * @return A ECKey containing only the public part, or {@code null} if recovery wasn't possible. */ static byte[] recoverPublicKeyECDSAFromSignature(int recId, BigInteger r, BigInteger s, byte[] messageHash) { if (!(recId == 0 || recId == 1)) { throw new IllegalArgumentException("Recovery Id must be 0 or 1 for secp256k1."); } if (r.signum() < 0 || s.signum() < 0) { throw new IllegalArgumentException("'r' and 's' shouldn't be negative."); } // 1.1 - 1.3 calculate point R ECPoint R = decompressKey(r, (recId & 1) == 1); // 1.4 nR should be a point at infinity if (R == null || !R.multiply(ECDSA_SECP256K1_DOMAIN.getN()).isInfinity()) { return null; } // 1.5 Compute e from M using Steps 2 and 3 of ECDSA signature verification. BigInteger e = new BigInteger(1, messageHash); // 1.6.1 Compute a candidate public key as: // Q = mi(r) * (sR - eG) // Where mi(x) is the modular multiplicative inverse. We transform this into the following: // Q = (mi(r) * s ** R) + (mi(r) * -e ** G) // Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n). // In the above equation ** is point multiplication and + is point addition (the EC group // operator). // // We can find the additive inverse by subtracting e from zero then taking the mod. For example the additive // inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod 11 = 8. BigInteger eInv = BigInteger.ZERO.subtract(e).mod(ECDSA_SECP256K1_DOMAIN.getN()); BigInteger rInv = r.modInverse(ECDSA_SECP256K1_DOMAIN.getN()); BigInteger srInv = rInv.multiply(s).mod(ECDSA_SECP256K1_DOMAIN.getN()); BigInteger eInvrInv = rInv.multiply(eInv).mod(ECDSA_SECP256K1_DOMAIN.getN()); ECPoint q = ECAlgorithms.sumOfTwoMultiplies(ECDSA_SECP256K1_DOMAIN.getG(), eInvrInv, R, srInv); if (q.isInfinity()) { return null; } return q.getEncoded(true); } private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { var X_9_INTEGER_CONVERTER = new X9IntegerConverter(); byte[] compEnc = X_9_INTEGER_CONVERTER.integerToBytes( xBN, 1 + X_9_INTEGER_CONVERTER.getByteLength(ECDSA_SECP256K1_DOMAIN.getCurve())); compEnc[0] = (byte) (yBit ? 0x03 : 0x02); try { return ECDSA_SECP256K1_DOMAIN.getCurve().decodePoint(compEnc); } catch (IllegalArgumentException e) { // the key was invalid return null; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy