您的位置:首页 > 移动开发 > IOS开发

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

2014-04-04 16:01 671 查看
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版短信发送程序,哈哈!









内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: