RSA非对称加密的秘密

535 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 8 天,点击查看活动详情

RSA非对称加密被广泛应用于银行等支付系统,对数据安全要求极高的一般采用RSA加密。RSA加密难以被破解的原因在于计算机对大数的因式分解的耗时是个天文数字。我们先来看看计算机都有哪些方式进行因式分解。

因式分解公式

平方差公式

比如21被分解成3x7就是通过它。

完全平方公式

比如2和3就可以被它分解出来。

立方和公式

立方差公式

完全立方公式

两根式

RSA加密的安全性

尽管计算机有这么多公式来进行因式分解,但我告诉你,1024位的私钥依旧安全,虽然512位的私钥不推荐,现有的超级计算机已经计算出了700多位的因式分解。你别看1024差700多不远,但你要知道越到后面的难度是指数级上升的,而且试错的时间跨度也是个天文数字。如果你还是担心的话,我建议你使用2048位的私钥。

RSA加密解密过程

首先定义N=p*q,且p和q均为质数,N就是能加密的最大的数字的上限。比如:

p=17,q=19

N=p*q=323

然后求私钥和公钥的上限L。L是p-1和q-1的最小公倍数。 那么我这里就是 (17-1) 和 (19-1) 的最小公倍数144,最小公倍数就是能够整除这两个数的最小的数。

我们定义私钥为e,公钥为d。有e * d mod L = 1。模以144等于1,我们就取个最小的145。e * d = 145,e和d都不能取1,那么我们假设生成的公钥为5,私钥为29,因为小的为公钥,大的为私钥。

1 < e < L

e和L最大公约数为1,即互质

1 < d < L

e * d mod L = 1

现有私钥e=29,公钥d=5,N=323。

公钥加密私钥解密

密文 = 明文 ^ 公钥 mod N

明文 = 密文 ^ 私钥 mod N

123(明文) ^ 5 % 323 = 225(密文)

225(密文) ^ 29 % 323 = 123(明文)

私钥加密公钥解密

密文 = 明文 ^ 私钥 mod N

明文 = 密文 ^ 公钥 mod N

和公钥加密私钥解密类似。

PKCS1和PKCS8的区别

PKCS1里面包含了RSA加密、解密、签名验签等所有的内容。PKCS8是一个专门用来存储私钥的文件格式规范,为PKCS新的标准。PKCS8和PKCS1的内容的区别在于,PKCS8定义了一个26个字节的文件的头,后面的数据和PKCS1完全一样。让我们再看一下Flutter中怎么获取RSA私钥和公钥的字符串。

static String encodePublicKeyToPemPKCS1(RSAPublicKey publicKey) {
  var topLevel = ASN1Sequence();

  topLevel.add(ASN1Integer(publicKey.modulus));
  topLevel.add(ASN1Integer(publicKey.exponent));

  var dataBase64 = base64.encode(topLevel.encodedBytes!);
  return """-----BEGIN RSA PUBLIC KEY-----\n$dataBase64\n-----END RSA PUBLIC KEY-----""";
}

static String encodePrivateKeyToPemPKCS1(RSAPrivateKey privateKey) {
  var topLevel = ASN1Sequence();
  var version = ASN1Integer(BigInt.from(0));
  var modulus = ASN1Integer(privateKey.n);
  var publicExponent = ASN1Integer(privateKey.exponent);
  var privateExponent = ASN1Integer(privateKey.d);
  var p = ASN1Integer(privateKey.p);
  var q = ASN1Integer(privateKey.q);
  var dP = privateKey.d! % (privateKey.p! - BigInt.from(1));
  var exp1 = ASN1Integer(dP);
  var dQ = privateKey.d! % (privateKey.q! - BigInt.from(1));
  var exp2 = ASN1Integer(dQ);
  var iQ = privateKey.q!.modInverse(privateKey.p!);
  var co = ASN1Integer(iQ);
  topLevel.add(version);
  topLevel.add(modulus);
  topLevel.add(publicExponent);
  topLevel.add(privateExponent);
  topLevel.add(p);
  topLevel.add(q);
  topLevel.add(exp1);
  topLevel.add(exp2);
  topLevel.add(co);

  var dataBase64 = base64.encode(topLevel.encode());

  return """-----BEGIN RSA PRIVATE KEY-----\n$dataBase64\n-----END RSA PRIVATE KEY-----""";
}

我们可以看到以-----BEGIN RSA PRIVATE KEY-----开头并以-----END RSA PRIVATE KEY-----结尾的为PKCS1格式的RSA私钥。没有RSA字符的像以-----BEGIN PRIVATE KEY-----开头的并以-----END PRIVATE KEY-----结尾的则为PKCS8格式的RSA私钥。

使用OpenSSL生成RSA私钥并转换成PKCS8格式

生成RSA的私钥

openssl genrsa -out pkcs1.pem 1024

将PKCS1的私钥转成PKCS8

openssl pkcs8 -topk8 -inform PEM -in pkcs1.pem -outform PEM -nocrypt -out pkcs8.pem

RSA的使用场景

比如IM即时通讯软件的聊天消息,在传输过程中,不被别人窃密。比如支付宝订单数据的加签和对该笔订单生成来源是否为支付宝客户端来源的验证。总之,私钥和公钥都可以用于加密和解密,使用私钥加密就要使用公钥解密,使用公钥加密就要使用私钥解密。你把公钥提供出去给别人进行加密,你可以使用私钥来验证是否使用公钥签名过,确保数据来源的可靠性。