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

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

/*-
 *
 * Hedera Java SDK
 *
 * Copyright (C) 2020 - 2022 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 com.google.errorprone.annotations.Var;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo;
import org.bouncycastle.asn1.pkcs.EncryptionScheme;
import org.bouncycastle.asn1.pkcs.KeyDerivationFunc;
import org.bouncycastle.asn1.pkcs.PBES2Parameters;
import org.bouncycastle.asn1.pkcs.PBKDF2Params;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;

import javax.annotation.Nullable;
import javax.crypto.Cipher;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.security.AlgorithmParameters;
import java.security.NoSuchAlgorithmException;

final class Pem {
    private static final String TYPE_PRIVATE_KEY = "PRIVATE KEY";
    private static final String TYPE_ENCRYPTED_PRIVATE_KEY = "ENCRYPTED PRIVATE KEY";

    private Pem() {
    }

    /*
     * For some reason, this generates PEM encodings that we ourselves can import, but OpenSSL
     * doesn't like. We decided to punt on generating encrypted PEMs for now but saving
     * the code for when we get back to it and/or any demand arises.
     */
    @SuppressWarnings("unused")
    static void writeEncryptedPrivateKey(PrivateKeyInfo pkInfo, Writer out, String passphrase) throws IOException {
        byte[] salt = Crypto.randomBytes(Crypto.SALT_LEN);

        KeyParameter derivedKey = Crypto.deriveKeySha256(
            passphrase, salt, Crypto.ITERATIONS, Crypto.CBC_DK_LEN);

        byte[] iv = Crypto.randomBytes(Crypto.IV_LEN);

        Cipher cipher = Crypto.initAesCbc128Encrypt(derivedKey, iv);

        byte[] encryptedKey = Crypto.runCipher(cipher, pkInfo.getEncoded());

        // I wanted to just do this with BC's PKCS8Generator and KcePKCSPBEOutputEncryptorBuilder
        // but it tries to init AES instance of `Cipher` with a `PBKDF2Key` and the former complains

        // So this is basically a reimplementation of that minus the excess OO
        PBES2Parameters parameters = new PBES2Parameters(
            new KeyDerivationFunc(
                PKCSObjectIdentifiers.id_PBKDF2,
                new PBKDF2Params(
                    salt,
                    Crypto.ITERATIONS,
                    Crypto.CBC_DK_LEN,
                    new AlgorithmIdentifier(PKCSObjectIdentifiers.id_hmacWithSHA256))),
            new EncryptionScheme(NISTObjectIdentifiers.id_aes128_CBC,
                ASN1Primitive.fromByteArray(cipher.getParameters().getEncoded())));

        EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(
            new AlgorithmIdentifier(PKCSObjectIdentifiers.id_PBES2, parameters),
            encryptedKey);

        PemWriter writer = new PemWriter(out);
        writer.writeObject(new PemObject(TYPE_ENCRYPTED_PRIVATE_KEY, encryptedPrivateKeyInfo.getEncoded()));
        writer.flush();
    }

    static PrivateKeyInfo readPrivateKey(Reader input, @Nullable String passphrase) throws IOException {
        PemReader pemReader = new PemReader(input);

        @Var PemObject readObject = null;

        for (; ; ) {
            PemObject nextObject = pemReader.readPemObject();

            if (nextObject == null) {
                break;
            }

            readObject = nextObject;

            String objType = readObject.getType();

            if (passphrase != null && !passphrase.isEmpty() && objType.equals(TYPE_ENCRYPTED_PRIVATE_KEY)) {
                return decryptPrivateKey(readObject.getContent(), passphrase);
            } else if (objType.equals(TYPE_PRIVATE_KEY)) {
                return PrivateKeyInfo.getInstance(readObject.getContent());
            }
        }

        if (readObject != null && readObject.getType().equals(TYPE_ENCRYPTED_PRIVATE_KEY)) {
            throw new BadKeyException("PEM file contained an encrypted private key but no passphrase was given");
        }

        throw new BadKeyException("PEM file did not contain a private key");
    }

    private static PrivateKeyInfo decryptPrivateKey(byte[] encodedStruct, String passphrase) throws IOException {
        var encryptedPrivateKeyInfo = EncryptedPrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(encodedStruct));

        AlgorithmIdentifier encryptAlg = encryptedPrivateKeyInfo.getEncryptionAlgorithm();

        if (!encryptAlg.getAlgorithm().equals(PKCSObjectIdentifiers.id_PBES2)) {
            throw new BadKeyException("unsupported PEM key encryption: " + encryptAlg);
        }

        PBES2Parameters params = PBES2Parameters.getInstance(encryptAlg.getParameters());
        KeyDerivationFunc kdf = params.getKeyDerivationFunc();
        EncryptionScheme encScheme = params.getEncryptionScheme();

        if (!kdf.getAlgorithm().equals(PKCSObjectIdentifiers.id_PBKDF2)) {
            throw new BadKeyException("unsupported KDF: " + kdf.getAlgorithm());
        }

        if (!encScheme.getAlgorithm().equals(NISTObjectIdentifiers.id_aes128_CBC)) {
            throw new BadKeyException("unsupported encryption: " + encScheme.getAlgorithm());
        }

        PBKDF2Params kdfParams = PBKDF2Params.getInstance(kdf.getParameters());

        if (!kdfParams.getPrf().getAlgorithm().equals(PKCSObjectIdentifiers.id_hmacWithSHA256)) {
            throw new BadKeyException("unsupported PRF: " + kdfParams.getPrf());
        }

        int keyLength = kdfParams.getKeyLength() != null
            ? kdfParams.getKeyLength().intValue()
            : Crypto.CBC_DK_LEN;

        KeyParameter derivedKey = Crypto.deriveKeySha256(
            passphrase,
            kdfParams.getSalt(),
            kdfParams.getIterationCount().intValue(),
            keyLength);

        AlgorithmParameters aesParams;
        try {
            aesParams = AlgorithmParameters.getInstance("AES");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        aesParams.init(encScheme.getParameters().toASN1Primitive().getEncoded());

        Cipher cipher = Crypto.initAesCbc128Decrypt(derivedKey, aesParams);
        byte[] decrypted = Crypto.runCipher(cipher, encryptedPrivateKeyInfo.getEncryptedData());

        // we need to parse our input data as the cipher may add padding
        ASN1InputStream inputStream = new ASN1InputStream(new ByteArrayInputStream(decrypted));
        return PrivateKeyInfo.getInstance(inputStream.readObject());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy