1、ASN.1 简介
ASN.1(Abstract Syntax Notation One ) 是一种接口描述语言,提供了一种平台无关的描述数据结构的方式。
ASN.1 是 ITU-T、ISO、以及 IEC 的标准,广泛应用于电信和计算机网络领域,尤其是密码学领域。
ASN.1 可以通过 schema 来定义数据结构,提供跨平台的数据序列化和反序列化能力。有大量的 RFC 文档使用 ASN.1 定义协议、数据格式等。比如 https 所使用的 X.509 证书结构,就是使用 ASN.1 定义的。
1.1、BER & DER 浅析
Basic Encoding Rules (BER) 是一种自描述的ASN.1数据结构的二进制编码格式。每一个编码后的BER数据依次由数据类型标识(Type identifier),长度描述(Length description), 实际数据(actual Value)排列而成,即BER是一种二进制TLV编码。TLV编码的一个好处,是数据的解析者不需要读取完整的数据,仅从一个不完整的数据流就可以开始解析。
Distinguished Encoding Rules (DER)是BER的子集,主要是消除了BER的一些不确定性的编码规则,比如在BER中Boolean类型true的value字节,可以为任何小于255大于0的整数,而在DER中,value字节只能为255。DER的这种确定性,保证了一个ASN.1数据结构,在编码为为DER后,只会有一种正确的结果。这使得DER更适合用在数字签名领域,比如X.509中广泛使用了DER。
关于各种ASN.1数据类型是如何被编码为DER,可以在这里找到详尽的解释。
如果有DER数据需要解析查看内容,这里有一个很方便的在线工具。
1.3、PEM 浅析
DER格式是ASN.1数据的二进制编码,计算机处理方便,但不利于人类处理,比如不方便直接在邮件正文中粘贴发送。PEM是DER格式的BASE64编码。除此之外,PEM在DER的BASE64前后各增加了一行,用来标识数据内容。示例如下:
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMYfnvWtC8Id5bPKae5yXSxQTt
+Zpul6AnnZWfI2TtIarvjHBFUtXRo96y7hoL4VWOPKGCsRqMFDkrbeUjRrx8iL91
4/srnyf6sh9c8Zk04xEOpK1ypvBz+Ks4uZObtjnnitf0NBGdjMKxveTq+VE7BWUI
yQjtQ8mbDOsiLLvh7wIDAQAB
-----END PUBLIC KEY-----
X.509 浅析
X.509是一项描述公钥证书结构的标准,广泛使用在HTTPS协议中,定义在RFC 3280
X.509使用ASN.1来描述公钥证书的结构,通常编码为DER格式,也可以进一步BASE64编码为可打印的PEM格式。V3版本的X.509结构如下:
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 }
ECC 公钥 DER 示例
我们以一个DER编码的ECC公钥为例,详细剖析一下X.509 ECC公钥的格式。公钥内容如下:
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
各个字节的含义如下所示
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)
移动端应用
JWK
JSON Web Key (JWK) 提供了一种使用 JSON(JavaScript Object Notation)表示加密密钥的标准方法。可以使用以下链接验证JWK所表示的密钥是否正确 8gwifi.org/jwkconvertf…
如果JSON能正常转化为PEM格式,即说明该JSON是正确的。
值得一提的是: JWK 中的value值是经过 Base64Url编码的,
例如下面的JSON
{
"use":"sig",
"kty":"RSA",
"kid":"51300370a8e1ac0a14a59cdd9c881d3f24c01f78",
"alg":"RS256",
"n":"w9OLNr_6sIuqr8OO3nPzorbv8tkmXC-m0k0O3W6gdk1QipvJ2pZkRSzD_iIWnEvYV11RuOBSAb7e_nU7mwNnxX6mORyJIEwnHKucBwaHQhuo56uVUjNTsRI6OuLB6REwxLM0ePPQPJNaXncWzt83oYdHU10VPmp5x0Sj-GjTvMpm2Y4I14KnFUXMEvIC-e5lf2P7q6KMXNw3PchEvmO5fVCgXf5-FgzDzEyn0qXrerdui4lGUtzcREPFksPNLNMlqp0XL5Kz1QLLkKDtR3dVjEtViEYJ6extcI-xFEV785hO4Ok36N99ht41EZk8ibrflNnYkJIEXAw_LKkmtxyZKw",
"e":"AQAB"
}
转化为PEM格式为
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw9OLNr/6sIuqr8OO3nPz
orbv8tkmXC+m0k0O3W6gdk1QipvJ2pZkRSzD/iIWnEvYV11RuOBSAb7e/nU7mwNn
xX6mORyJIEwnHKucBwaHQhuo56uVUjNTsRI6OuLB6REwxLM0ePPQPJNaXncWzt83
oYdHU10VPmp5x0Sj+GjTvMpm2Y4I14KnFUXMEvIC+e5lf2P7q6KMXNw3PchEvmO5
fVCgXf5+FgzDzEyn0qXrerdui4lGUtzcREPFksPNLNMlqp0XL5Kz1QLLkKDtR3dV
jEtViEYJ6extcI+xFEV785hO4Ok36N99ht41EZk8ibrflNnYkJIEXAw/LKkmtxyZ
KwIDAQAB
-----END PUBLIC KEY-----
Base64Url编码
Base64Url编码,就是先将二进制数据进行 Base64编码,因为 + / =在网络传输中存在诸多不便,因此将 + 替换为-、/替换为_、 将末尾的=全部删除。
base64UrlEncode(data: Uint8Array): string {
let base64Str = Base64.encodeToString(data);
base64Str = base64Str.replaceAll("+", "-")
base64Str = base64Str.replaceAll("/", "_")
while (base64Str.length > 1 && base64Str.endsWith("=")) {
base64Str = base64Str.substring(0, base64Str.length - 1)
}
return base64Str;
}
iOS使用ECC
生成ECC密钥对
在iOS中,支持多种算法,如下所示
extern const CFStringRef kSecAttrKeyTypeRSA
API_AVAILABLE(macos(10.7), ios(2.0));
extern const CFStringRef kSecAttrKeyTypeDSA
API_AVAILABLE(macos(10.7), ios(NA));
extern const CFStringRef kSecAttrKeyTypeAES
API_AVAILABLE(macos(10.7), ios(NA));
extern const CFStringRef kSecAttrKeyTypeDES
API_AVAILABLE(macos(10.7), ios(NA));
extern const CFStringRef kSecAttrKeyType3DES
API_AVAILABLE(macos(10.7), ios(NA));
extern const CFStringRef kSecAttrKeyTypeRC4
API_AVAILABLE(macos(10.7), ios(NA));
extern const CFStringRef kSecAttrKeyTypeRC2
API_AVAILABLE(macos(10.7), ios(NA));
extern const CFStringRef kSecAttrKeyTypeCAST
API_AVAILABLE(macos(10.7), ios(NA));
extern const CFStringRef kSecAttrKeyTypeECDSA
API_AVAILABLE(macos(10.7), ios(NA));
extern const CFStringRef kSecAttrKeyTypeEC
API_AVAILABLE(macos(10.9), ios(4.0));
extern const CFStringRef kSecAttrKeyTypeECSECPrimeRandom
API_AVAILABLE(macos(10.12), ios(10.0));
使用EC非对称加密算法如下所示
SecKeyRef privateKey = SecKeyCreateRandomKey((CFDictionaryRef) @{
(__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeECSECPrimeRandom,
(__bridge id) kSecAttrKeySizeInBits : @(256),
(__bridge id) kSecAttrTokenID : (__bridge id)kSecAttrTokenIDSecureEnclave,
}
} , &createError);
导出公钥
SecKeyRef key = [self loadPrivateKeyWithSecureKey:secureKey];
SecKeyRef public = SecKeyCopyPublicKey(key);
}
将密钥转化为JWK
使用 kSecAttrKeyTypeECSECPrimeRandom 返回的密钥格式是 ANSI X9.63
具体格式为 (04 || X || Y [ || K])。 公钥的格式为 04 || X || Y
+ (NSDictionary * _Nullable) jwkPublicKeyWithSecKey:(SecKeyRef) key{
NSDictionary *result = nil;
CFErrorRef error = NULL;
CFDataRef pubKeyData = SecKeyCopyExternalRepresentation(key, &error);
if (!pubKeyData) { return nil; }
NSData *data = (__bridge NSData *)pubKeyData;
const char *p = [data bytes];
// kSecAttrKeyTypeECSECPrimeRandom ANSI X9.63 format (04 || X || Y [ || K])
// note: public key is just (04 || X || Y).
// we expect a p256 key or greater, with leading 04 byte
if (data.length > 64 && p && *p++ == 0x04) {
NSUInteger Xlength = (data.length - 1) / 2;
NSUInteger Ylength = (data.length - 1) - Xlength;
NSData *Xdata = [NSData dataWithBytes:p length:Xlength];
NSData *Ydata = [NSData dataWithBytes:p+Xlength length:Ylength];
NSString *X = [self base64URLEncodedStringRepresentationWithData:Xdata];
NSString *Y = [self base64URLEncodedStringRepresentationWithData:Ydata];
//NSString *K = @"com.apple.acmeclient"; //%%% this needs to be passed in
NSDictionary *jwkDict = @{
@"kty" : @"EC",
@"crv" : @"P-256",
@"x" : X,
@"y" : Y,
//@"kid": K,
};
result = jwkDict;
}
return result;
}
鸿蒙使用ECC
在鸿蒙系统中,主要借助于 ohos.security.huks (通用密钥库系统)
生成ECC密钥对
import { huks } from '@kit.UniversalKeystoreKit';
/* 以生成ECC256密钥为例 */
let keyAlias: string = 'keyAlias';
let properties: Array<huks.HuksParam> =[
{
tag: huks.HuksTag.HUKS_TAG_ALGORITHM,
value: huks.HuksKeyAlg.HUKS_ALG_ECC
},
{
tag: huks.HuksTag.HUKS_TAG_KEY_SIZE,
value: huks.HuksKeySize.HUKS_ECC_KEY_SIZE_256
},
{
tag: huks.HuksTag.HUKS_TAG_PURPOSE,
value:
huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_SIGN |
huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_VERIFY
},
{
tag: huks.HuksTag.HUKS_TAG_DIGEST,
value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256
},
];
let options: huks.HuksOptions = {
properties: properties
};
try {
huks.generateKeyItem(keyAlias, options, (error, data) => {
if (error) {
console.error(`callback: generateKeyItem failed`);
} else {
console.info(`callback: generateKeyItem key success`);
}
});
} catch (error) {
console.error(`callback: generateKeyItem input arg invalid`);
}
导出公钥
-
指定密钥别名keyAlias,密钥别名最大长度为64字节。
-
调用接口exportKeyItem,传入参数keyAlias和options。 options为预留参数,当前可传入空。
-
返回值为HuksReturnResult类型对象,获取的公钥明文在outData字段中,以标准的
X.509规范的DER格式封装,具体请参考公钥材料格式。
import { huks } from '@kit.UniversalKeystoreKit';
/* 1. 设置密钥别名 */
let keyAlias = 'keyAlias';
/* option对象传空 */
let emptyOptions: huks.HuksOptions = {
properties: []
};
try {
/* 2. 导出密钥 */
huks.exportKeyItem(keyAlias, emptyOptions, (error, data) => {
if (error) {
console.error(`callback: exportKeyItem failed, ` + error);
} else {
console.info(`callback: exportKeyItem success, data = ${JSON.stringify(data)}`);
}
});
} catch (error) {
console.error(`callback: exportKeyItem input arg invalid, ` + JSON.stringify(error));
}
导出的公钥结果如下
let eccP256PubKey = new Uint8Array([
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, 0xc0, 0xfe, 0x1c, 0x67, 0xde,
0x86, 0x0e, 0xfb, 0xaf, 0xb5, 0x85, 0x52, 0xb4, 0x0e, 0x1f, 0x6c, 0x6c, 0xaa, 0xc5, 0xd9, 0xd2,
0x4d, 0xb0, 0x8a, 0x72, 0x24, 0xa1, 0x99, 0xaf, 0xfc, 0x3e, 0x55, 0x5a, 0xac, 0x99, 0x3d, 0xe8,
0x34, 0x72, 0xb9, 0x47, 0x9c, 0xa6, 0xd8, 0xfb, 0x00, 0xa0, 0x1f, 0x9f, 0x7a, 0x41, 0xe5, 0x44,
0x3e, 0xb2, 0x76, 0x08, 0xa2, 0xbd, 0xe9, 0x41, 0xd5, 0x2b, 0x9e
]);
将密钥转化为JWK
在文章的开头ECC 公钥 DER 示例,我们已经了解过X509规范 DER格式的组成,其最后64位数字,分别是ECPoint X 和 ECPoint Y。
async exportJWK() {
let result = await this.exportKeyItem(ecaliakey)
let pubKeyData = result.outData ?? new Uint8Array(0)
pubKeyData = pubKeyData.subarray(pubKeyData.length - 64, pubKeyData.length);
let xLength = (pubKeyData?.length ) / 2
let xData = this.pubKeyData.subarray(0, xLength)
let yData = this.pubKeyData.subarray(xLength, pubKeyData.length )
let x = this.base64UrlEncode(xData)
let y = this.base64UrlEncode(yData)
let jwkDict: Map<string, object> = new Map();
jwkDict["kty"] = "EC"
jwkDict["crv"] = "P-256"
jwkDict["x"] = x
jwkDict["y"] = y
return jwkDict;
}
至此,我们对密码学中的非对称加密算法ECC和其在移动端的应用做了一个简单的介绍,看完这篇文章,相信在以后的工作中,如果遇到 X509、ANS1、DER格式、JWK等相关问题时,或许能对你有些帮助。
参考:
blog.csdn.net/liwei16611/… 8gwifi.org/jwkconvertf…
如果觉得有收获请按如下方式给个
爱心三连:👍:点个赞鼓励一下。🌟:收藏文章,方便回看哦!。💬:评论交流,互相进步!。