Longitud de clave privada de curva elíptica en Java

Aug 25 2020

Creé un par de claves EC usando la curva "secp256r1". Es una curva de 256 bits y la clave privada debe ser de 256 bits (32 bytes). Pero lo que obtengo es una clave privada de 39 bytes. Aquí está mi 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 

Estoy usando JDK 1.8.0_144 y la biblioteca BouncyCastle. Aquí 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> 

¿Cómo obtener 32 bytes de clave privada?

Respuestas

4 dave_thompson_085 Aug 25 2020 at 06:06

La parte dependiente del algoritmo de PKCS8, en el elemento #2 de la secuencia, para EC es en sí misma una codificación DER, de la estructura SEC1 ECPrivateKey, también documentada en rfc5915 . Para SunEC, esta es una SECUENCIA de versión INTEGER y OCTETSTRING que contiene los bytes de clave privada reales; se omiten los parámetros opcionales context-0 y context-1 publickey. Entonces necesitas analizar eso:

    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 está usando BouncyCastle, en lugar de analizar manualmente el PKCS8, puede usar la clase 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);

y finalmente, en lugar de pasar por la codificación, puede obtener el valor de la clave privada directamente del ECPrivateKeyobjeto como un BigIntegervalor, que se puede convertir en una matriz de bytes, aunque tiene una longitud variable en lugar de la longitud fija que se usa convencionalmente para las claves privadas de EC, por lo que es posible que deba ajustarlo:

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