Detailed explanation of ECC public key format

This article first introduces several concepts/technologies related to public key format, then analyzes ECC public key in DER format by way of example, and finally introduces how to generate, parse and use ECC public key using Java.

ASN.1

Abstract Syntax Notation One (ASN.1) is an interface description language that provides a platform-independent way of describing data structures. ASN.1 is a standard of ITU-T, ISO, and IEC, and is widely used in the field of telecommunications and computer networks, especially in the field of cryptography.

ASN.1 is very similar to the familiar Protocol Buffers and Apache Thrift . Both can define data structures through schema and provide cross-platform data serialization and deserialization capabilities. The difference is that ASN.1 was set as a standard as early as 1984, many years earlier than the two, and has been widely used. It is used to define many data structures that are widely used in the world. There are a large number of The RFC document uses ASN.1 to define protocols, data formats, etc. For example, the X.509 certificate structure used by https is defined using ASN.1.

ASN.1 defines several basic data types and structure types:

Topic Description
Basic Types BIT STRING
BOOLEAN
INTEGER
NULL
OBJECT IDENTIFIER
OCTET STRING
String Types BMPString
IA5String
PrintableString
TeletexString
UTF8String
Constructed Types SEQUENCE
SET
CHOICE

The above mentioned basic types can be found here in detail. We can use these to describe our own data structures:

    FooQuestion ::= SEQUENCE {
        trackingNumber INTEGER,
        question       IA5String
    }

A data structure called FooQuestion is defined above. It is a SEQUENCE structure that contains an INTEGER and an IA5String
. A concrete FooQuestion can be described as:

    myQuestion FooQuestion ::= {
        trackingNumber     5,
        question           "Anybody there?"
    }

The data structure instance defined by ASN.1 can be serialized into binary BER, text type JSON, XML, etc.

Object Identifier

Object Identifier (OID) is a standard developed by ITU and ISO/IEC to uniquely identify objects, concepts, or anything else with globally unique properties.

An OID is represented by a string of numbers separated by . For example, the OID of the elliptic curve secp256r1 is like this:

1.2.840.10045.3.1.7

The meaning of each number is as follows:

iso(1) member-body(2) us(840) ansi-X9-62(10045) curves(3) prime(1) 7

OIDs are distributed uniformly globally, all OIDs can be regarded as a multi-fork tree, and each valid OID is represented as a node on the tree. All current OIDs can be found here .

OID is the basic type of ASN.1.

BER & DER

Basic Encoding Rules (BER) is a self-describing binary encoding format for ASN.1 data structures. Each encoded BER data is arranged in turn by a data type identifier (Type identifier), a length description (Length description), and an actual data (actual value), that is, BER is a binary TLV encoding. One benefit of TLV encoding is that the parser of the data does not need to read the complete data, but can start parsing from an incomplete data stream.

Distinguished Encoding Rules (DER) is a subset of BER, mainly to eliminate some uncertain encoding rules of BER, such as the value byte of Boolean type true in BER, which can be any integer less than 255 and greater than 0, and in BER In DER, the value byte can only be 255. This certainty of DER ensures that an ASN.1 data structure will only have one correct result after being encoded as DER. This makes DER more suitable for use in the field of digital signatures, such as DER is widely used in X.509.

A detailed explanation of how various ASN.1 data types are encoded into DER can be found here .

If there is DER data that needs to be parsed to view the content, there is a very convenient online tool .

Use DER to encode the custom myQuestion in the ASN.1 section as follows:

0x30 0x13 0x02 0x01 0x05 0x16 0x0e 0x41 0x6e 0x79 0x62 0x6f 064 0x79 0x20 0x74 0x68 0x65 0x72 0x65 0x3f
---  ---  ---  ---  ---  ---  ---  --------------------------------------------------------------------
 ^    ^    ^    ^    ^    ^    ^                                   ^
 |    |    |    |    |    |    |                                   |
 |    |    | INTEGER | IA5STRING                                   |
 |    |    | LEN=1   | TAG     |                                   |
 |    |    |         |         |                                   |
 |    | INTEGER   INTEGER   IA5STRING                          IA5STRING
 |    | TAG       VALUE(5)  LEN=14                             VALUE("Anybody there?")
 |    |
 |    |  ----------------------------------------------------------------------------------------------
 |    |                                              ^
 |  SEQUENCE LEN=19                                  |
 |                                                   |
SEQUENCE TAG                                  SEQUENCE VALUE

PEM

The DER format is the binary encoding of ASN.1 data, which is convenient for computer processing, but not conducive to human processing. For example, it is not convenient to paste and send directly in the body of the email. PEM is BASE64 encoding in DER format . In addition, PEM adds a line before and after BASE64 of DER to identify the data content. An example is as follows:

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMYfnvWtC8Id5bPKae5yXSxQTt
+Zpul6AnnZWfI2TtIarvjHBFUtXRo96y7hoL4VWOPKGCsRqMFDkrbeUjRrx8iL91
4/srnyf6sh9c8Zk04xEOpK1ypvBz+Ks4uZObtjnnitf0NBGdjMKxveTq+VE7BWUI
yQjtQ8mbDOsiLLvh7wIDAQAB
-----END PUBLIC KEY-----

X.509

X.509 is a standard describing the structure of public key certificates , widely used in the HTTPS protocol, defined in RFC 3280

X.509 uses ASN.1 to describe the structure of public key certificates, usually encoded in DER format, and can be further BASE64 encoded into printable PEM format. The X.509 structure of the V3 version is as follows:

    Certificate  ::=  SEQUENCE  {
        tbsCertificate       TBSCertificate,
        signatureAlgorithm   AlgorithmIdentifier,
        signatureValue       BIT STRING  }

    TBSCertificate  ::=  SEQUENCE  {
        version         [0]  EXPLICIT Version DEFAULT v1,
        serialNumber         CertificateSerialNumber,
        signature            AlgorithmIdentifier,
        issuer               Name,
        validity             Validity,
        subject              Name,
        subjectPublicKeyInfo SubjectPublicKeyInfo,
        issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
                             -- If present, version MUST be v2 or v3
        subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
                             -- If present, version MUST be v2 or v3
        extensions      [3]  EXPLICIT Extensions OPTIONAL
                             -- If present, version MUST be v3
        }

   Version  ::=  INTEGER  {  v1(0), v2(1), v3(2)  }

   CertificateSerialNumber  ::=  INTEGER

   Validity ::= SEQUENCE {
        notBefore      Time,
        notAfter       Time }

   Time ::= CHOICE {
        utcTime        UTCTime,
        generalTime    GeneralizedTime }

   UniqueIdentifier  ::=  BIT STRING

   SubjectPublicKeyInfo  ::=  SEQUENCE  {
        algorithm            AlgorithmIdentifier,
        subjectPublicKey     BIT STRING  }

   Extensions  ::=  SEQUENCE SIZE (1..MAX) OF Extension

   Extension  ::=  SEQUENCE  {
        extnID      OBJECT IDENTIFIER,
        critical    BOOLEAN DEFAULT FALSE,
        extnValue   OCTET STRING  }

SubjectPublicKeyInfo

As shown in the previous section, SubjectPublicKeyInfo is part of the public key certificate format X.509. The SubjectPublicKeyInfo structure is described using ASN.1, and the SubjectPublicKeyInfo structure using the elliptic curve public-private key encryption algorithm is defined in RFC 5480

Its structure is as follows:

   SubjectPublicKeyInfo  ::=  SEQUENCE  {
        algorithm            AlgorithmIdentifier,
        subjectPublicKey     BIT STRING
   }

   AlgorithmIdentifier  ::=  SEQUENCE  {
        algorithm   OBJECT IDENTIFIER,
        parameters  ANY DEFINED BY algorithm OPTIONAL
   }

It can be seen that the AlgorithmIdentifier is also a SEQUENCE, and its parameters part depends on the specific value of the algorithm.

For scenarios where the algorithm is used for unlimited ECC public keys, the value of algorithm is:

1.2.840.10045.2.1

即: iso(1) member-body(2) us(840) ansi-X9-62(10045) keyType(2) 1

In this type of scenario, parameters are defined as follows:

    ECParameters ::= CHOICE {
        namedCurve         OBJECT IDENTIFIER
    }

That is, parameters specify the elliptic curve used by the ECC public key. Its optional values ​​are:

    secp192r1 OBJECT IDENTIFIER ::= {
        iso(1) member-body(2) us(840) ansi-X9-62(10045) curves(3) prime(1) 1 }

    sect163k1 OBJECT IDENTIFIER ::= {
        iso(1) identified-organization(3) certicom(132) curve(0) 1 }

    sect163r2 OBJECT IDENTIFIER ::= {
        iso(1) identified-organization(3) certicom(132) curve(0) 15 }

    secp224r1 OBJECT IDENTIFIER ::= {
        iso(1) identified-organization(3) certicom(132) curve(0) 33 }

    sect233k1 OBJECT IDENTIFIER ::= {
        iso(1) identified-organization(3) certicom(132) curve(0) 26 }

    sect233r1 OBJECT IDENTIFIER ::= {
        iso(1) identified-organization(3) certicom(132) curve(0) 27 }

    secp256r1 OBJECT IDENTIFIER ::= {
        iso(1) member-body(2) us(840) ansi-X9-62(10045) curves(3) prime(1) 7 }

    sect283k1 OBJECT IDENTIFIER ::= {
        iso(1) identified-organization(3) certicom(132) curve(0) 16 }

    sect283r1 OBJECT IDENTIFIER ::= {
        iso(1) identified-organization(3) certicom(132) curve(0) 17 }

    secp384r1 OBJECT IDENTIFIER ::= {
        iso(1) identified-organization(3) certicom(132) curve(0) 34 }

    sect409k1 OBJECT IDENTIFIER ::= {
        iso(1) identified-organization(3) certicom(132) curve(0) 36 }

    sect409r1 OBJECT IDENTIFIER ::= {
        iso(1) identified-organization(3) certicom(132) curve(0) 37 }

    secp521r1 OBJECT IDENTIFIER ::= {
        iso(1) identified-organization(3) certicom(132) curve(0) 35 }

    sect571k1 OBJECT IDENTIFIER ::= {
        iso(1) identified-organization(3) certicom(132) curve(0) 38 }

    sect571r1 OBJECT IDENTIFIER ::= {
        iso(1) identified-organization(3) certicom(132) curve(0) 39 }

After the algorithm is determined, let's look at the subjectPublicKey. For the ECC public key, the subjectPublicKey is the ECPoint:

    ECPoint ::= OCTET STRING

It is an OCTET STRING with a length of 65 bytes, the first byte of which indicates whether the ECPoint is compressed. If it is 0x04, it means no compression. The remaining 64 bytes, the first 32 bytes, represent the X coordinate of the ECPoint, and the last 32 bytes represent the Y coordinate of the ECPoint.

When an ECPoint of type OCTET STRING is converted to subjectPublicKey of type BIT STRING, it is converted in big-endian byte order.

ECC Public Key Example

Let's take a DER-encoded ECC public key as an example to analyze the format of the X.509 ECC public key in detail. The contents of the public key are as follows:

0x30 0x59 0x30 0x13 0x06 0x07 
0x2a 0x86 0x48 0xce 0x3d 0x02 
0x01 0x06 0x08 0x2a 0x86 0x48 
0xce 0x3d 0x03 0x01 0x07 0x03 
0x42 0x00 0x04 0x13 0x32 0x8e 
0x0c 0x11 0x8a 0x70 0x1a 0x9e 
0x18 0xa3 0xa9 0xa5 0x65 0xd8 
0x41 0x68 0xce 0x2f 0x5b 0x11 
0x94 0x57 0xec 0xe3 0x67 0x76 
0x4a 0x3f 0xb9 0xec 0xd1 0x15 
0xd0 0xf9 0x56 0x8b 0x15 0xe6 
0x06 0x2d 0x72 0xa9 0x45 0x56 
0x99 0xb0 0x9b 0xb5 0x30 0x90 
0x8d 0x2e 0x31 0x0e 0x95 0x68 
0xcc 0xcc 0x19 0x5c 0x65 0x53 
0xba

Through the previous introduction, we already know that this is a DER encoding of SubjectPublicKeyInfo in ASN.1 format, which is a TLV type of binary data. Now let's parse layer by layer:

0x30 (SEQUENCE TAG: SubjectPublicKeyInfo) 0x59 (SEQUENCE LEN=89)
        0x30 (SEQUENCE TAG: AlgorithmIdentifier) 0x13 (SEQUENCE LEN=19)
                0x06 (OID TAG: Algorithm) 0x07 (OID LEN=7)
                        0x2a 0x86 0x48 0xce 0x3d 0x02 0x01 (OID VALUE="1.2.840.10045.2.1": ecPublicKey/Unrestricted Algorithm Identifier)
                0x06 (OID TAG: ECParameters:NamedCurve) 0x08 (OID LEN=8)
                        0x2a 0x86 0x48 0xce 0x3d 0x03 0x01 0x07 (OID VALUE="1.2.840.10045.3.1.7": Secp256r1/prime256v1)
        0x03 (BIT STRING TAG: SubjectPublicKey:ECPoint) 0x42 (BIT STRING LEN=66) 0x00 (填充bit数量为0)
                0x04 (未压缩的ECPoint)
                0x13 0x32 0x8e 0x0c 0x11 0x8a 0x70 0x1a 0x9e 0x18 0xa3 0xa9 0xa5 0x65 0xd8 0x41 0x68 0xce 0x2f 0x5b 0x11 0x94 0x57 0xec 0xe3 0x67 0x76 0x4a 0x3f 0xb9 0xec 0xd1 (ECPoint:X)
                0x15 0xd0 0xf9 0x56 0x8b 0x15 0xe6 0x06 0x2d 0x72 0xa9 0x45 0x56 0x99 0xb0 0x9b 0xb5 0x30 0x90 0x8d 0x2e 0x31 0x0e 0x95 0x68 0xcc 0xcc 0x19 0x5c 0x65 0x53 0xba (ECPoint:Y)

Java Code

This section provides sample codes related to using Java to generate ECC public and private keys, encoding and decoding ECC public and private keys, using ECC for signature verification, and encryption and decryption for reference. There are a few things to note when using the ECC algorithm in Java:

  • JDK1.7 has built-in ECC public and private key generation and signature verification, but does not implement encryption and decryption, so it is necessary to use BouncyCastle as the Security Provider;
  • Using high-level encryption and decryption algorithms in Java, such as AES using 256bit keys, ECC using Secp256r1, etc., needs to update the JRE security policy file, otherwise an error like "Illegal key size or default parameters" will be reported. For details on how to change the policy file, please refer to here
  • In the actual project development process, it may be found that the public key passed to Java is not a complete X.509 SubjectPublicKeyInfo. For example, only a 65-byte ECPoint is passed. In this case, you can communicate with the other party about the Algorithm and NamedCurve used. After completing the DER data, use the Java Security library to parse it.
JDK 1.7
依赖:org.bouncycastle:bcprov-jdk15on:1.59

import com.sun.jersey.core.util.Base64;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.binary.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ECCUtils {

  private static final Logger LOGGER = LoggerFactory.getLogger(ECCUtils.class);
  private static final String PROVIDER = "BC";

  private static final byte[] PUB_KEY_TL= new byte[26];

  static {
    Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

    try {
      KeyPair keyPair = genKeyPair();
      PublicKey publicKeyExample = keyPair.getPublic();
      System.arraycopy(publicKeyExample.getEncoded(), 0, PUB_KEY_TL, 0, 26);
    } catch (Exception e) {
      LOGGER.error("无法初始化算法", e);
    }
  }

  public static KeyPair genKeyPair() throws NoSuchAlgorithmException, NoSuchProviderException {
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", PROVIDER);
    keyPairGenerator.initialize(256, new SecureRandom());
    return keyPairGenerator.generateKeyPair();
  }

  public static String encodePublicKey(PublicKey publicKey) {
    return StringUtils.newStringUtf8(Base64.encode(publicKey.getEncoded()));
  }

  public static PublicKey decodePublicKey(String keyStr)
      throws NoSuchProviderException, NoSuchAlgorithmException {
    byte[] keyBytes = getPubKeyTLV(keyStr);

    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
    KeyFactory keyFactory = KeyFactory.getInstance("EC", PROVIDER);
    try {
      return keyFactory.generatePublic(keySpec);
    } catch (InvalidKeySpecException e) {
      LOGGER.error("无效的ECC公钥", e);
      return null;
    }
  }

  private static byte[] getPubKeyTLV(String keyStr) {
    byte[] keyBytes = Base64.decode(StringUtils.getBytesUtf8(keyStr));

    if(keyBytes.length == 65) {
      byte[] tlv = new byte[91];
      System.arraycopy(PUB_KEY_TL, 0, tlv, 0, 26);
      System.arraycopy(keyBytes, 0, tlv, 26, 65);
      return tlv;
    }

    return keyBytes;
  }

  public static String encodePrivateKey(PrivateKey privateKey) {
    return StringUtils.newStringUtf8(Base64.encode(privateKey.getEncoded()));
  }

  public static PrivateKey decodePrivateKey(String keyStr)
      throws NoSuchProviderException, NoSuchAlgorithmException {
    byte[] keyBytes = Base64.decode(StringUtils.getBytesUtf8(keyStr));
    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
    KeyFactory keyFactory = KeyFactory.getInstance("EC", PROVIDER);
    try {
      return keyFactory.generatePrivate(keySpec);
    } catch (InvalidKeySpecException e) {
      LOGGER.error("无效的ECC私钥", e);
      return null;
    }
  }

  public static byte[] encrypt(byte[] content, PublicKey publicKey)
      throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, NoSuchProviderException {
    Cipher cipher = Cipher.getInstance("ECIES", PROVIDER);
    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
    return cipher.doFinal(content);
  }

  public static byte[] decrypt(byte[] content, PrivateKey privateKey)
      throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, NoSuchProviderException {
    Cipher cipher = Cipher.getInstance("ECIES", PROVIDER);
    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    return cipher.doFinal(content);
  }

  public static byte[] signature(byte[] content, PrivateKey privateKey)
      throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
    Signature signature = Signature.getInstance("SHA256withECDSA");
    signature.initSign(privateKey);
    signature.update(content);
    return signature.sign();
  }

  public static boolean verify(byte[] content, byte[] sign, PublicKey publicKey)
      throws NoSuchAlgorithmException, InvalidKeyException {
    Signature signature = Signature.getInstance("SHA256withECDSA");
    signature.initVerify(publicKey);
    try {
      signature.update(content);
      return signature.verify(sign);
    } catch (SignatureException e) {
      LOGGER.warn("无效的签名", e);
      return false;
    }

  }
}

Summary: There are many standards and protocols related to cryptography, and the principles often require some mathematical foundation. It may be easy to get the program to work right away, but it takes some time to figure out how it works.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324996432&siteId=291194637