来源:http://theosoft.net/
RSA是一种比较常用的非对称加密算法,其原理是基于大整数因数分解的计算安全,这里不做介绍。非对称加密的好处在于其密码分为公钥和私钥两部分,你可以随意分发你的公钥,让用户用来加密数据;等上传到服务器端后再用私钥就可以解密里面的数据。所以,这样的体系特别适合用于客户端–尤其使手机客户端的数据加密,而不用担心你的程序被反编译、破解后泄露了你的密码。
正是因为有着这么好的安全特性,早在年初,我还在使用windows mobile手机的时候,就把RSA算法写入了我的那个利用飞信发短信的客户端里。每当要发送短信时,客户端负责使用公钥将短信内容加密,并连同收件人一起传输到接收数据的服务器上。服务器收到信息后直接存储到数据库里。然后由另一台专门负责短信发送的服务器,每隔1分钟查询一次数据库,发现有需要发送的短信后,用私钥将其解密,并通过飞信发送出去。无论是否发送成功,最终都会再发送一条短信,向我报告本次发送的结果。
自从 10月份换了iphone 4后,第一周我就写了一个 ios上不加密的同样功能的程序,但是RSA的加密算法却让我纠结了很长时间。因为原来使用windows mobile的时候,RSA的公钥是以原始数据的形式保存在文件里的。但是到了ios上,苹果似乎并不允许直接使用原始的密钥。google了很久,虽然找了一个可能可行的办法,但是因为非常复杂,需要先将密钥导入手机内的密钥库,然后再取出来,试了很多次一直都没成功。后来在网上某位大牛的指点下发现,虽然ios无法直接使用原始的密钥加密数据,但是对证书的支持确非常好,于是花了2天的时间研究了使用证书加密数据的方法,效果非常好,呵呵。下面简单说一下使用证书加密数据的方法。
首先当然是要生成一张证书。微软的.Net framework SDK为我们提供了一个生成X.509数字证书的命令行工具Makecert.exe。
打开.Net的控制台,使用如下命令生成证书:
makecert -sr LocalMachine -ss My -n CN=Theoservice -sky exchange -pe
Makecert命令的详细说明请参看微软Makecert.exe工具的文档:http://msdn.microsoft.com/library/chs/default.asp?url=/library/CHS/cptools/html/cpgrfcertificatecreationtoolmakecertexe.asp
这样就生成了一张名为Theoservice的证书到你的电脑里。然后,开始->运行->MMC,打开MMC控制台。文件->添加/删除管理单元->添加按钮->选”证书”->添加->选”计算机账户”->关闭->确定,然后你就可以在 “个人->证书” 里看到刚才生成的证书了。证书采用1024位密钥加密。现在,你需要做得就是导出这张证书。如果你的服务器并不是本机,你首先需要导出一个带私钥的pfx格式的证书。导出时需要你填写密码来保护这张证书,然后将其导入到服务器上就好了。此外,你还需要导出一份不带私钥的cer格式的证书。这张证书只含有公钥,是用来和客户端一起发布出去用来加密数数据的。
证书已经有了,接下来就要写算法来加密和解秘数据了。先来看看C#服务器端的解密过程:
public class RSAHandler
{
const string CERT = “Theoservice”;
const int KEYLENGTH = 128;
const int BLOCKSIZE = KEYLENGTH - 11;
private static X509Certificate2 GetRSACertificate()
{
X509Certificate2 clientCert = null;
if (clientCert == null)
{
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
foreach (var certificate in store.Certificates)
{
if (certificate.GetNameInfo(X509NameType.SimpleName, false) == CERT)
{
clientCert = certificate;
break;
}
}
}
return clientCert;
}
public static RSACryptoServiceProvider GetPrivateKey()
{
var clientCert = GetRSACertificate();
var publicKey = (RSACryptoServiceProvider)clientCert.PrivateKey;
return publicKey;
}
public static string RSADecrypt(string rawText, RSACryptoServiceProvider rsa)
{
try
{
var encryptedBytes = Convert.FromBase64String(rawText);
int numBlock = encryptedBytes.Length / KEYLENGTH;
byte[] rawResult = new byte[0];
var buffer = new byte[KEYLENGTH];
for (var i = 0; i < numBlock; i++)
{
Array.Copy(encryptedBytes, i * KEYLENGTH, buffer, 0, buffer.Length);
var decryptedBytes = rsa.Decrypt(buffer, false);
var resultBuffer = new byte[rawResult.Length + decryptedBytes.Length];
Array.Copy(rawResult, resultBuffer, rawResult.Length);
Array.Copy(decryptedBytes, 0, resultBuffer, rawResult.Length, decryptedBytes.Length);
rawResult = resultBuffer;
}
var plaintext = Encoding.UTF8.GetString(rawResult);
return plaintext;
}
catch (CryptographicException e)
{
return e.Message;
}
}
}
这个类里面有两个public的函数:GetPrivateKey()和RSADecrypt()。前者用来从电脑的密钥库中找到指定的证书,并取出该证书的私钥;后者则使用密钥将数据解密。为了方便在存储和传输,所有加密后的数据都采用base64编码,所以RSADecrypt在解密数据之前首先做base64解码,然后计算密文的长度,分组解密。这里需要普及一下RSA分组加密的知识:因为采用了1024位的密钥,所以密钥长度为1024/8=128个byte。而C#默认采用#PKSC1的padding模式,每次最多可以加密128-11=117个byte。也就是说,RSA分组加密算法每次从明文里取<=117个byte,然后加密成128个byte的密文;解密的时候,每次就取128个byte的密文,将其解密成<=117个byte的明文。因为#PKSC1的padding模式每次padding的内容是随机的,所以即使是加密同一段明文,每次的结果也不一样,这大大的增加了数据安全性。最后把所有解密的结果连起来,识别成utf-8格式的字符串,就是我们之前加密的明文了。
再来看看手机客户端的加密算法:
- (SecKeyRef)getPublicKey{
NSString *certPath = [[NSBundle mainBundle] pathForResource:@”Theoservice” ofType:@”cer”];
SecCertificateRef myCertificate = nil;
NSData *certificateData = [[NSData alloc] initWithContentsOfFile:certPath];
myCertificate = SecCertificateCreateWithData(kCFAllocatorDefault, (CFDataRef)certificateData);
SecPolicyRef myPolicy = SecPolicyCreateBasicX509();
SecTrustRef myTrust;
OSStatus status = SecTrustCreateWithCertificates(myCertificate,myPolicy,&myTrust);
SecTrustResultType trustResult;
if (status == noErr) {
status = SecTrustEvaluate(myTrust, &trustResult);
}
return SecTrustCopyPublicKey(myTrust);
}
- (NSData *)encrypt:(NSString *)plainText usingKey:(SecKeyRef)key error:(NSError **)err
{
size_t cipherBufferSize = SecKeyGetBlockSize(key);
uint8_t *cipherBuffer = NULL;
cipherBuffer = malloc(cipherBufferSize * sizeof(uint8_t));
memset((void *)cipherBuffer, 0×0, cipherBufferSize);
NSData *plainTextBytes = [plainText dataUsingEncoding:NSUTF8StringEncoding];
int blockSize = cipherBufferSize – 11;
int numBlock = (int)ceil([plainTextBytes length] / (double)blockSize);
NSMutableData *encryptedData = [[NSMutableData alloc] init];
for (int i=0; i<numBlock; i++) {
int bufferSize = MIN(blockSize,[plainTextBytes length] – i * blockSize);
NSData *buffer = [plainTextBytes subdataWithRange:NSMakeRange(i * blockSize, bufferSize)];
OSStatus status = SecKeyEncrypt(key, kSecPaddingPKCS1,
(const uint8_t *)[buffer bytes],
[buffer length], cipherBuffer,
&cipherBufferSize);
if (status == noErr)
{
NSData *encryptedBytes = [[[NSData alloc]
initWithBytes:(const void *)cipherBuffer
length:cipherBufferSize] autorelease];
[encryptedData appendData:encryptedBytes];
}
else
{
*err = [NSError errorWithDomain:@"errorDomain" co
NSLog(@”encrypt:usingKey: Error: %d”, status);
return nil;
}
}
if (cipherBuffer)
{
free(cipherBuffer);
}
NSLog(@”Encrypted text (%d bytes): %@”,
[encryptedData length], [encryptedData description]);
return encryptedData;
}
加密算法和解密差不多,就是每次取117个byte的明文并加密成128个byte的密文,最后连起来做base64编码。需要注意的是公钥的获取方法getPublicKey。前面不是导出了一张只含有公钥的cer格式的证书吗?现在把它加到程序的Resources里,这里取名Theoservice.cer。然后通过[[NSBundle mainBundle] pathForResource:@”Theoservice” ofType:@”cer”]就可以把里面的内容读出来,生成SecCertificateRef实体。然后用分别创建一个SecPolicyRef和SecTrustRef,就可以获取到这个公钥的SecKeyRef了,其实还是蛮简单的,不是吗?!