iOS安全相关 - RSA public key的处理

RSA部分基础

通常我们所说的RSA公钥1024位,2048位是RSA算法的模长度,https的TSL ssl部分也是使用RSA来进行加密握手.

RSA基本原理(略过)

$ openssl req -x509 -out public_key.der -outform der -new -newkey rsa:1024 -keyout private_key.pem -days 3650
  • public_key.der is an output based on x509 certificate. Note that in iOS must be .der format but not .pem
  • private_key.pem is the private key that you can use it to decrypt
  • rsa:1024 有时候是2048.is the key length. The longer the length, the safer it is
  • days is the days for effective period for this cert. In this case, is 10-Years

然后,将public_key.der拖到iOS project然后使用RSA.h RSA.m .

参考: http://jslim.net/blog/2013/01/05/rsa-encryption-in-ios-and-decrypt-it-using-php/

RSA加密中的Padding

padding即是填充方式,先说下为什么会有padding,由于RSA的算法原理中,需要被加密的明文c是要比模数小的。padding就是通过一些填充方式保证明文c的位数,且不能使c大于n.因此模的长度也觉得可以加密明文的长度,因此RSA不适合加密大段文本,一般用来加密一个对称加密的密钥,然后再用此对称加密密钥对大段文本加密。一般选择模式是RSA_PKCS1_PADDING

IOS中的RSA

在ios中可以使用可实现些RSA的加密解密,但是framework的api只支持从标准证书文件(cer, crt)中读取公钥.

//加密方法
OSStatus SecKeyEncrypt(
    SecKeyRef           key,
    SecPadding          padding,
    const uint8_t       *plainText,
    size_t              plainTextLen,
    uint8_t             *cipherText,
    size_t              *cipherTextLen)
    __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_2_0);

// 解密方法
OSStatus SecKeyDecrypt(
    SecKeyRef           key,    
    SecPadding          padding,
    const uint8_t       *cipherText,
    size_t              cipherTextLen,
    uint8_t             *plainText, 
    size_t              *plainTextLen)  

iOS拿到的公钥类型

第一种: pem格式文件 或者 base64的 public key

一个pem格式的公钥文件,其中保存的公钥信息是base64编码的字符串,这个用iOS 系统sercurity.framework的api就不能读取。

后台经常会给一个xxx.pem的文件. 里面的内容用sublime打开就如上所示.
下面是pem内容示例(ps: 每行64个字符)

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApfNDz78E4qnU23PeE3lQ
DYaW9AtniNMWNn51eMANFtiFj6yvpAo+ejstkC4u0PevoA9YrhPUC97f8xd5Jss2
INyynKH3okVS4at5lxBBdpoqR3LxLSQPRcmh5h19PZj0/B6QO1Lm66qoxjoQUjoQ
4eqryb0zD5vwu+6kCQHH/sHSky8ZYdUH9p5baJOmLUFpqROPWZpliq7qxFTuLLoZ
jHEgbdPad/IX9IouYzrlnhQzUP+WjugnLU6NoeoKUer9YtPZg1WVbg/n98orsb8H
hmJL/RtAqWpOPMvEoYLGyiguFEPgjvYPV2dnSJxkTgb3XqnnujTN1BFsYDWmMC9S
TQIDAQAB
-----END PUBLIC KEY-----

实际, 上述文件中每行结尾都有一个隐藏的换行符\n,如果把换行符加上,然后写成一个NSString:

NSSring *pemPublicKey = @"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApfNDz78E4qnU23PeE3lQ\nDYaW9AtniNMWNn51eMANFtiFj6yvpAo+ejstkC4u0PevoA9YrhPUC97f8xd5Jss2\nINyynKH3okVS4at5lxBBdpoqR3LxLSQPRcmh5h19PZj0/B6QO1Lm66qoxjoQUjoQ\n4eqryb0zD5vwu+6kCQHH/sHSky8ZYdUH9p5baJOmLUFpqROPWZpliq7qxFTuLLoZ\njHEgbdPad/IX9IouYzrlnhQzUP+WjugnLU6NoeoKUer9YtPZg1WVbg/n98orsb8H\nhmJL/RtAqWpOPMvEoYLGyiguFEPgjvYPV2dnSJxkTgb3XqnnujTN1BFsYDWmMC9S\nTQIDAQAB\n-----END PUBLIC KEY-----";

这种是上面pem格式中间部分, 同时也不包含回车换行, 需要每64个字符添加一个换行符.

// base64编码的字符串
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApfNDz78E4qnU23PeE3lQDYaW9AtniNMWNn51eMANFtiFj6yvpAo+ejstkC4u0PevoA9YrhPUC97f8xd5Jss2INyynKH3okVS4at5lxBBdpoqR3LxLSQPRcmh5h19PZj0/B6QO1Lm66qoxjoQUjoQ4eqryb0zD5vwu+6kCQHH/sHSky8ZYdUH9p5baJOmLUFpqROPWZpliq7qxFTuLLoZjHEgbdPad/IX9IouYzrlnhQzUP+WjugnLU6NoeoKUer9YtPZg1WVbg/n98orsb8HhmJL/RtAqWpOPMvEoYLGyiguFEPgjvYPV2dnSJxkTgb3XqnnujTN1BFsYDWmMC9STQIDAQAB

第二种: public key 的16进制字符串

第二种,后台可能直接给Public Key 的16进制的字符串(Hex String), 或者 2进制字符串, 这种公钥一般称为裸公钥, HJ就是提供的这种中key.

16进制字符串表示的Public key

一共512位16进制,2进制位2048位(ps: 也有256位16进制,2进制1024位的public key).这种裸公钥相当于上面pem格式中的中间部分(除了-BEGIN- -END-两行, 并且中间部分不包含\n回车换行符), 只不过二进制/十六进制数据通过进行了base64编码成字符串,用sublime可以直接打开.

下面是一个16进制字符串public key, 提供512位16进制的字符串:

E00B364554A35D6705468698DC366115C09F2BA109D1F3A45901EF8872429752A0DAED95F50B31977FCF1989BC105143444D4583A479478F89148F1A35DACBACAC9584BFC528BE1AFF94685177514468F96F707BCBCD4F76DF989E5D7223384593AE81A1F2254D7CDD1CCA7BA01B006C4BAD87C2D270C51BF48B6CCECFA7022CAE93E9B17D31F0DB7A59E68D103BC99A3DDCA411CDDF2BC5558C7A88A884EFD33E52253C6D9E050F54C813A5EF38688C7A547072685399E5759FCFD03FD22B21E58D583C4D46EC5582A78FE4E18C10AF081970EEB14FEEDAA04F315D216EF988CB16F84FBBEE767CD51EDFFB23E83B303AD715B6E7A9D6CC475496F9BA3951A5

这种模式比较好存储, 我们可以通过编写代码将这种格式的数据转化成第一种格式(PEM)格式,

将裸Public Key转化成为需要

  1. 为裸 public_key 添加完整的头部和尾部
  2. 头部:30820122300D06092A864886F70D01010105000382010F003082010A0282010100
  3. 尾部: 0203010001
  4. 然后执行base64编码,然后每一行64位换行处理,加标准头和尾部就是标准的PEM文件的中间部分。

在Terminal中直接运行下面命令可以得到base64编码的public key
16进制字符串显示 -> 二进制字符串显示 -> base64编码

echo 30820122300D06092A864886F70D01010105000382010F003082010A0282010100E00B364554A35D6705468698DC366115C09F2BA109D1F3A45901EF8872429752A0DAED95F50B31977FCF1989BC105143444D4583A479478F89148F1A35DACBACAC9584BFC528BE1AFF94685177514468F96F707BCBCD4F76DF989E5D7223384593AE81A1F2254D7CDD1CCA7BA01B006C4BAD87C2D270C51BF48B6CCECFA7022CAE93E9B17D31F0DB7A59E68D103BC99A3DDCA411CDDF2BC5558C7A88A884EFD33E52253C6D9E050F54C813A5EF38688C7A547072685399E5759FCFD03FD22B21E58D583C4D46EC5582A78FE4E18C10AF081970EEB14FEEDAA04F315D216EF988CB16F84FBBEE767CD51EDFFB23E83B303AD715B6E7A9D6CC475496F9BA3951A50203010001 | xxd -r -ps | openssl base64

输出的结果是:

MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4As2RVSjXWcFRoaY3DZh
FcCfK6EJ0fOkWQHviHJCl1Kg2u2V9Qsxl3/PGYm8EFFDRE1Fg6R5R4+JFI8aNdrL
rKyVhL/FKL4a/5RoUXdRRGj5b3B7y81Pdt+Ynl1yIzhFk66BofIlTXzdHMp7oBsA
bEuth8LScMUb9Itszs+nAiyuk+mxfTHw23pZ5o0QO8maPdykEc3fK8VVjHqIqITv
0z5SJTxtngUPVMgTpe84aIx6VHByaFOZ5XWfz9A/0ish5Y1YPE1G7FWCp4/k4YwQ
rwgZcO6xT+7aoE8xXSFu+YjLFvhPu+52fNUe3/sj6DswOtcVtuep1sxHVJb5ujlR
pQIDAQAB

然后手动添加首部和尾部,最终结果就和上面PEM格式内容一样了:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApfNDz78E4qnU23PeE3lQ
DYaW9AtniNMWNn51eMANFtiFj6yvpAo+ejstkC4u0PevoA9YrhPUC97f8xd5Jss2
INyynKH3okVS4at5lxBBdpoqR3LxLSQPRcmh5h19PZj0/B6QO1Lm66qoxjoQUjoQ
4eqryb0zD5vwu+6kCQHH/sHSky8ZYdUH9p5baJOmLUFpqROPWZpliq7qxFTuLLoZ
jHEgbdPad/IX9IouYzrlnhQzUP+WjugnLU6NoeoKUer9YtPZg1WVbg/n98orsb8H
hmJL/RtAqWpOPMvEoYLGyiguFEPgjvYPV2dnSJxkTgb3XqnnujTN1BFsYDWmMC9S
TQIDAQAB
-----END PUBLIC KEY-----

第二种解决方法, 直接转化成证书der格式的数据

例如,随便找一张der证书, 将它转化成的16进制字符串展示:

30820308308201f00209008932a038393835a1300d06092a864886f70d01010505003045310b30090603550406130241553113301106035504080c0a536f6d652d53746174653121301f060355040a0c18496e7465726e6574205769646769747320507479204c74643020170d3135303631303037333931355a180f32313135303531373037333931355a3045310b30090603550406130241553113301106035504080c0a536f6d652d53746174653121301f060355040a0c18496e7465726e6574205769646769747320507479204c746430820122300d06092a864886f70d01010105000382010f003082010a0282010100

*E00B364554A35D6705468698DC366115C09F2BA109D1F3A45901EF8872429752A0DAED95F50B31977FCF1989BC105143444D4583A479478F89148F1A35DACBACAC9584BFC528BE1AFF94685177514468F96F707BCBCD4F76DF989E5D7223384593AE81A1F2254D7CDD1CCA7BA01B006C4BAD87C2D270C51BF48B6CCECFA7022CAE93E9B17D31F0DB7A59E68D103BC99A3DDCA411CDDF2BC5558C7A88A884EFD33E52253C6D9E050F54C813A5EF38688C7A547072685399E5759FCFD03FD22B21E58D583C4D46EC5582A78FE4E18C10AF081970EEB14FEEDAA04F315D216EF988CB16F84FBBEE767CD51EDFFB23E83B303AD715B6E7A9D6CC475496F9BA3951A5*

0203010001300d06092a864886f70d0101050500038201010045c2e98898344a645889e227c112bbbaf09a66105e5e964a90f298420e4187510b4c13c705177ebf605a09c7df74b2ed51fe117a2ad291ee554ecd38db3c92ec06e9b5c1df27ca519d5c0330986bf7b6447e5e8e87607febef6ace24111a308840543458a52b365ac6ac392bf009df15bbba44155d095ddb711dd9599bb10c5d8a55f8531d09fa5be8db2dcdbcce869ea04a4dbfcd693f1f67689c3b1c259a3ece586008a69cdae17892cdcb6a11d45680b6854721c185937b24295b0fa115b9751020f65d6bfda0f4fb47a8982e80c7c3682daa113ef24bae4a7e82aaa2aa634bab8faa68b28b3d999720dc9c2fc272dc488d2256f0cd1dd8a13a400ab9dd6c
  • 找到30820122300D06092A864886F70D01010105000382010F003082010A0282010100 (这段代表-----BEGIN PUBLIC KEY-----)
  • 找到尾部'0203010001' (-----END PUBLIC KEY-----)
  • 两者之间刚好512位16进制数据,就是证书的2048位 publi key所在的地方. 直接用自己的16进制字符串public key换掉这部分,就伪造了一个后缀是.der的证书

注意: .der证书内还包括很多信息,例如地址.公司,过期时间.等等,一般rsa加密只用中间256字节(2048位)的public key就行了

其他参考内容 - 后台给出的 java 参考

后台给出的使用public key的参考:

String publicKeyStr = "30820122300D06092A864886F70D01010105000382010F003082010A0282010100"
+ "E00B364554A35D6705468698DC366115C09F2BA109D1F3A45901EF8872429752A0DAED95F50B31977FCF1989BC105143444D4583A479478F89148F1A35DACBACAC9584BFC528BE1AFF94685177514468F96F707BCBCD4F76DF989E5D7223384593AE81A1F2254D7CDD1CCA7BA01B006C4BAD87C2D270C51BF48B6CCECFA7022CAE93E9B17D31F0DB7A59E68D103BC99A3DDCA411CDDF2BC5558C7A88A884EFD33E52253C6D9E050F54C813A5EF38688C7A547072685399E5759FCFD03FD22B21E58D583C4D46EC5582A78FE4E18C10AF081970EEB14FEEDAA04F315D216EF988CB16F84FBBEE767CD51EDFFB23E83B303AD715B6E7A9D6CC475496F9BA3951A5"
+ "0203010001";

一般js中给的是裸公钥,即512位16进制(实际为二进制的2048位)

因为NDK中实现加密和解密,需要完整的公钥和私钥,所以需要相应的头部和尾部。

头部:30820122300D06092A864886F70D01010105000382010F003082010A0282010100

尾部: 0203010001

然后执行base64编码,然后每一行64位换行处理,加标准头和尾部就是标准的公钥了。

base64编码的命令: termial中,直接运行下面命令可以得到标准的公钥:

echo 30820122300D06092A864886F70D01010105000382010F003082010A0282010100A5F343CFBF04E2A9D4DB73DE1379500D8696F40B6788D316367E7578C00D16D8858FACAFA40A3E7A3B2D902E2ED0F7AFA00F58AE13D40BDEDFF3177926CB3620DCB29CA1F7A24552E1AB79971041769A2A4772F12D240F45C9A1E61D7D3D98F4FC1E903B52E6EBAAA8C63A10523A10E1EAABC9BD330F9BF0BBEEA40901C7FEC1D2932F1961D507F69E5B6893A62D4169A9138F599A658AAEEAC454EE2CBA198C71206DD3DA77F217F48A2E633AE59E143350FF968EE8272D4E8DA1EA0A51EAFD62D3D98355956E0FE7F7CA2BB1BF0786624BFD1B40A96A4E3CCBC4A182C6CA282E1443E08EF60F576767489C644E06F75EA9E7BA34CDD4116C6035A6302F524D0203010001| xxd -r -ps | openssl base64

其他参考文档:

关于PEM格式的public key 进行加密, 解密可以参考以下开源库:

https://github.com/ideawu/Objective-C-RSA

https://github.com/kuapay/iOS-Certificate--Key--and-Trust-Sample-Project

后面两个项目需要引入openssl库

https://blog.cnbluebox.com/blog/2014/03/19/rsajia-mi/

https://github.com/NianJi/BBRSACryptor

你可能感兴趣的:(iOS安全相关 - RSA public key的处理)