Análise e Conversão de Formatos de Assinatura SM2: Raw vs ASN.1 DER

Cotnexto e Tamanhos de Assinatura no SM2

O algoritmo de criptografia de chave pública baseado em curva elíptica SM2 é amplamente utilizado em padrões de segurança. Durante o processo de geração de assinatura, a etapa final consiste em transformar dois valores numéricos (geralmente denominados R e S) em uma representação em string.

A assinatura SM2 pura (formato Raw) possui exatamente 128 caracteres hexadecimais, resultante da concatenação de R e S, cada um com 64 caracteres (32 bytes). No entanto, é comum encontrar assinaturas com comprimentos superiores a 128 caracteres. Isso ocorre porque os valores foram encapsulados utilizando a codificação ASN.1 no formato DER (Distinguished Encoding Rules).

Comparação entre os Formatos Raw e DER

Ao integrar diferentes módulos criptográficos, como bibliotecas de software (soft crypto) e módulos de segurança de hardware (HSM), é frequente observar discrepâncias no tamanho da assinatura gerada.

// Assinatura gerada por biblioteca de software (142 caracteres - Formato DER)
3045022100d596d18be77035b0bb9ef6abf232e9e81f2df3178bedd56d64220dc72c6883a602201b92ddc4c167e22956e5ef32ce19bf4c05f9d6d96aa82c41ace0ba28acba8715

// Assinatura gerada por HSM (128 caracteres - Formato Raw)
d596d18be77035b0bb9ef6abf232e9e81f2df3178bedd56d64220dc72c6883a61b92ddc4c167e22956e5ef32ce19bf4c05f9d6d96aa82c41ace0ba28acba8715

A diferença de tamanho deve-se estritamente à adição de metadados estruturais do ASN.1. O formato de 142 caracteres representa a estrutura DER, enquanto o de 128 caracteres é a concatenação direta dos valores hexadecimais.

Modelagem da Estrutura ASN.1

Como a assinatura SM2 é composta por dois inteiros matemáticos, a estrutura ASN.1 correspondente deve ser uma sequência contendo dois objetos do tipo ASN1Integer. Utilizando a biblioteca BouncyCastle em Java, podemos modelar essa entidade da seguinte forma:

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.DERSequence;

public class Sm2SignatureStructure extends ASN1Object {
    
    private final ASN1Integer componentR;
    private final ASN1Integer componentS;

    public Sm2SignatureStructure(ASN1Integer r, ASN1Integer s) {
        this.componentR = r;
        this.componentS = s;
    }

    @Override
    public ASN1Primitive toASN1Primitive() {
        ASN1EncodableVector sequenceVector = new ASN1EncodableVector();
        sequenceVector.add(this.componentR);
        sequenceVector.add(this.componentS);
        return new DERSequence(sequenceVector);
    }
}

Conversão de Raw para DER

Para transformar uma assinatura Raw de 128 caracteres em seu equivalente DER, dividimos a string hexadecimal em duas metades de 64 caracteres. Cada metade é convertida para um BigInteger e, subsequentemente, encapsulada na estrutura ASN.1 definida anteriormente.

import org.bouncycastle.asn1.ASN1Integer;
import java.math.BigInteger;

public class Sm2FormatConverter {

    private static final int TOTAL_RAW_LENGTH = 128;
    private static final int HALF_RAW_LENGTH = 64;

    public static String rawToDer(String rawHexSignature) {
        if (rawHexSignature == null || rawHexSignature.length() != TOTAL_RAW_LENGTH) {
            throw new IllegalArgumentException("A assinatura Raw deve ter exatamente 128 caracteres hexadecimais.");
        }

        String hexR = rawHexSignature.substring(0, HALF_RAW_LENGTH);
        String hexS = rawHexSignature.substring(HALF_RAW_LENGTH);

        BigInteger valueR = new BigInteger(hexR, 16);
        BigInteger valueS = new BigInteger(hexS, 16);

        Sm2SignatureStructure asn1Structure = new Sm2SignatureStructure(
                new ASN1Integer(valueR),
                new ASN1Integer(valueS)
        );

        try {
            byte[] derEncodedBytes = asn1Structure.toASN1Primitive().getEncoded();
            return bytesToHex(derEncodedBytes).toUpperCase();
        } catch (Exception ex) {
            throw new RuntimeException("Falha ao codificar a estrutura ASN.1", ex);
        }
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder(2 * bytes.length);
        for (byte b : bytes) {
            hexString.append(String.format("%02x", b));
        }
        return hexString.toString();
    }
}

Conversão de DER para Raw

O processo inverso exige a decodificação dos bytes DER para extrair a sequência ASN.1. É fundamental validar se o objeto decodificado é de fato uma sequência e se contém exatamente dois inteiros, extraindo seus valores e formatando-os novamente como strings hexadecimais de 64 caracteres.

import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import java.math.BigInteger;

public class Sm2FormatConverter {

    public static String derToRaw(String derHexSignature) {
        try {
            byte[] derBytes = hexToBytes(derHexSignature);
            ASN1Primitive primitiveObj = ASN1Primitive.fromByteArray(derBytes);

            if (!(primitiveObj instanceof ASN1Sequence)) {
                throw new IllegalArgumentException("O formato DER não representa uma sequência ASN.1 válida.");
            }

            ASN1Sequence sequence = (ASN1Sequence) primitiveObj;
            if (sequence.size() != 2) {
                throw new IllegalArgumentException("A sequência deve conter exatamente dois inteiros para SM2.");
            }

            BigInteger extractedR = ((ASN1Integer) sequence.getObjectAt(0)).getValue();
            BigInteger extractedS = ((ASN1Integer) sequence.getObjectAt(1)).getValue();

            String formattedR = String.format("%064x", extractedR);
            String formattedS = String.format("%064x", extractedS);

            return (formattedR + formattedS).toUpperCase();

        } catch (Exception ex) {
            throw new RuntimeException("Falha ao decodificar a assinatura DER", ex);
        }
    }

    private static byte[] hexToBytes(String hex) {
        int len = hex.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
                                 + Character.digit(hex.charAt(i+1), 16));
        }
        return data;
    }
}

A manipulação correta dos tipos ASN.1 é crucial. Diferentes estruturas criptográficas, como envelopes digitais ou certificados, exigem mapeamentos distintos (por exemplo, uso de DEROctetString ou DERBitString). Para assinaturas SM2, a regra estrita é o uso de ASN1Integer dentro de uma DERSequence.

Tags: SM2 asn1 bouncycastle java der-encoding

Publicado em 8-15 10:34