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.