基于RSA算法的ios客户端加密和C#服务端解密的解决方案

http://hi.baidu.com/xiezuan/item/75faac1466cc88debe904241

来源: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" code:status userInfo:nil];

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了,其实还是蛮简单的,不是吗?!

最后,来show一下最新的iphone版短信发送程序,哈哈!

    

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值