jsencrypt代码分析
jsencrypt代码分析——openssl的rsa加密解密在js的实现
在js上做rsa,感觉jsencrypt这个是封装的比较好的,但用起来还是遇到了些坑,所以踩进代码里填填坑~
项目在这里 https://github.com/travist/jsencrypt
【rsa算法】
首先科普一下rsa:公钥私钥成对,用其中一个加密只能用另一个解密,常用公钥加密私钥解密。
一开始看到斯坦佛那个库,原始的算法实现:
长度,建议至少1024。模数n(常取默认65537)两边都要用。
指数e,和n一起就是公钥。指数d,和n一起就是私钥。质数p和q用于生成密钥对,然后就丢弃不公开。
具体算法网上一大把,就不多说了。
【ssl/ssh密钥对】
用服务器上的工具生成的密钥对,格式一般是如下,有开始行结束行,内容用base64转码。这里涉及一些国际编码规范,代码分析时在逐一解释。
-----BEGIN PUBLIC KEY----- MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIA4OVgB4FRq4l5zjEmd4r/jswRcHlZQ kg10p9rzG3VyXCPpa/ZkwOYy+kGq7a7BjAKTpic2cUNRim4m8HKTdc8CAwEAAQ== -----END PUBLIC KEY-----
【代码分析】
加载公钥:setPublicKey->setKey->JSEncryptRSAKey->parseKey
RSAKey.prototype.parseKey = function (pem) {...}
注释写得非常好!
/** ...省略...
*This method accepts public key * in the rsaencryption pkcs #1 format (oid: 1.2.840.113549.1.1.1). * The format is defined as: * PublicKeyInfo ::= SEQUENCE { * algorithm AlgorithmIdentifier, * PublicKey BIT STRING * } * Where AlgorithmIdentifier is: * AlgorithmIdentifier ::= SEQUENCE { * algorithm OBJECT IDENTIFIER, the OID of the enc algorithm * parameters ANY DEFINED BY algorithm OPTIONAL (NULL for PKCS #1) * } * and PublicKey is a SEQUENCE encapsulated in a BIT STRING * RSAPublicKey ::= SEQUENCE { * modulus INTEGER, -- n * publicExponent INTEGER -- e * } */
代码如下:
1 RSAKey.prototype.parseKey = function (pem) { 2 try { 3 ... 4 var der = reHex.test(pem) ? Hex.decode(pem) : Base64.unarmor(pem); 5 var asn1 = ASN1.decode(der); 6 ... 7 if (asn1.sub.length === 9) { 8 // Parse the private key. 9 } 10 else if (asn1.sub.length === 2) { 11 // Parse the public key. 12 var bit_string = asn1.sub[1]; 13 var sequence = bit_string.sub[0]; 14 modulus = sequence.sub[0].getHexStringValue(); 15 this.n = parseBigInt(modulus, 16); 16 public_exponent = sequence.sub[1].getHexStringValue(); 17 this.e = parseInt(public_exponent, 16); 18 } 19 else { 20 return false; 21 } 22 return true; 23 } 24 catch (ex) { 25 return false; 26 } 27 };
这里需要了解一下各种编码格式。hex和base64就不解释了。
ASN.1抽象语法标记,我的理解就是对数据进行结构化解析的规范:一个标准的ASN.1编码对象有四个域:对象标识域、数据长度域、数据域以及结束标志(可选,在长度不可知情况下需要,openssl中没有该标志)。
DER则是具体的编码实现。 http://baike.baidu.com/view/100318.htm#4
PKCS#1则是RSA中最基础的算法定义和密钥规定,讲人话就是定义了:公钥是元组(n,e),算法是n=q*p等一系列公式。https://en.wikipedia.org/wiki/PKCS1
这段代码两处亮点,一个是else if (asn1.sub.length === 2)判断公钥(没深究,反正靠子节点数判断);
两一个是var bit_string = asn1.sub[1];即前面有一段asn1.sub[0]是算法标识。
这里本身没有坑,但是生成公钥时如果命令不对应是会踩坑的(有一种命令是生成无算法标记的公钥)~~~
加密:encrypt->RSAEncrypt->pkcs1pad2 | doPublic->RSADoPublic
pkcs1pad2是做补位处理:
入参 var m = pkcs1pad2(text,(this.n.bitLength()+7)>>3); 是(模位数+7)/8得到模的字节长度?
然后是一系列的移位操作,此处的作用是填入随机位使得每次加密的密文都不一样。http://blog.chinaunix.net/uid-21880738-id-1813144.html
RSADoPublic最终执行加密算法x^e (mod n)
至此,主要的算法、规范和坑基本上覆盖了,私钥和解密坑比较少,就不多说了。
最后,附送一个千年大坑,跨语言跨类库的时候不小心可能会遇到的 http://blog.chinaunix.net/uid-23069658-id-4282969.html
于这个js类库而言,-pubout出来的是可用的,而-RSAPublicKey_out出来的是不可用的。
与此对应,服务器openssl的类库中PEM_read_RSA_PUBKEY()读入是对应的,而PEM_read_RSAPublicKEY()读入是不对应的。