Comprimento da chave privada da curva elíptica em Java

Aug 25 2020

Criei um par de chaves EC usando a curva "secp256r1". É uma curva de 256 bits e a chave privada deve ser de 256 bits (32 bytes). Mas o que estou obtendo é uma chave privada de 39 bytes. Aqui está o meu código

 KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC"); //Provider is SunEC version 1.8
 ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
 kpg.initialize(ecSpec, new SecureRandom());
 KeyPair ecKeyPair = kpg.generateKeyPair();
 PrivateKey privateKey = ecKeyPair.getPrivate();
 
 ASN1Sequence sequence = DERSequence.getInstance(privateKey.getEncoded());
 DEROctetString subjectPrivateKey =  (DEROctetString) sequence.getObjectAt(2);
 byte[] privateKeyBytes = subjectPrivateKey.getOctets();

 System.out.println("PrivateKeyBytes.length: " + privateKeyBytes.length); // Expected length is 32, but actual is 39 

Estou usando o JDK 1.8.0_144 e a biblioteca BouncyCastle. Aqui está pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>pki</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-debug-jdk15on</artifactId>
            <version>1.65</version>
        </dependency>
    </dependencies>
</project> 

Como obter 32 bytes de chave privada?

Respostas

4 dave_thompson_085 Aug 25 2020 at 06:06

A parte dependente do algoritmo de PKCS8, no elemento #2 da sequência, para EC é ela própria uma codificação DER, da estrutura SEC1 ECPrivateKey, também documentada em rfc5915 . Para SunEC, esta é uma SEQUÊNCIA de versão INTEGER e OCTETSTRING contendo os bytes de chave privada reais; os parâmetros context-0 opcionais e a chave pública context-1 são omitidos. Então você precisa analisar isso:

    KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC",args[0]);
    kpg.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom());
    KeyPair ecKeyPair = kpg.generateKeyPair();
    PrivateKey privateKey = ecKeyPair.getPrivate();
    byte[] pkcs8 = privateKey.getEncoded();
    
    // slightly reorganized
    DEROctetString wrapped = (DEROctetString) DERSequence.getInstance(pkcs8).getObjectAt(2);
    System.out.println (wrapped.getOctets().length);
    // added
    DEROctetString raw = (DEROctetString) DERSequence.getInstance( wrapped.getOctets() ).getObjectAt(1);
    System.out.println (raw.getOctets().length);

Como você está usando o BouncyCastle, em vez de analisar manualmente o PKCS8, você pode usar a classe BouncyCastle para org.bouncycastle.asn1.pkcs.PrivateKeyInfo:

    PrivateKeyInfo info = PrivateKeyInfo.getInstance (DERSequence.getInstance(pkcs8));
    DEROctetString raw2 = (DEROctetString)( DERSequence.getInstance(info.parsePrivateKey()) ).getObjectAt(1);
    System.out.println (raw2.getOctets().length);

e, finalmente, em vez de passar pela codificação, você pode obter o valor da chave privada diretamente do ECPrivateKeyobjeto como um BigIntegervalor, que pode ser convertido em uma matriz de bytes, embora seja de comprimento variável em vez do comprimento fixo convencionalmente usado para chaves privadas EC, então pode ser necessário ajustá-lo:

    byte[] bytes = ((ECPrivateKey)privateKey).getS().toByteArray();
    System.out.println(bytes.length);
    // may need left-trim or pad with zero(s)