首先了解一下幾個相關概念,以方便後面遇到的問題的解決:
RSA算法:1977年由Ron Rivest、Adi Shamirh和LenAdleman發明的,RSA就是取自他們三個人的名字。算法基於一個數論:將兩個大素數相乘非常容易,但要對這個乘積的結果進行因式分解卻非常困難,因此可以把乘積公開作爲公鑰。該算法能夠抵抗目前已知的所有密碼***。RSA算法是一種非對稱算法,算法需要一對密鑰,使用其中一個加密,需要使用另外一個才能解密。我們在進行RSA加密通訊時,就把公鑰放在客戶端,私鑰留在服務器。
DER, PEM:既然使用RSA需要一對密鑰,那麼我們當然是要先使用工具來生成這樣一對密鑰了。在linux、unix下,最簡單方便的就是使用openssl命令行了。而DER、PEM就是生成的密鑰可選擇的兩種文件格式。DER是Distinguished Encoding Rules的簡稱,是一種信息傳輸語法規則,在ITU X.690中定義的。在ios端,我們的公鑰就是需要這樣一種格式的,我們可以從Certificate, Key, and Trust Services Reference這篇文檔的SecCertificateCreateWithData函數的data參數的說明中看到。而PEM格式是一種對DER進行封裝的格式,他只是把der的內容進行了base64編碼並加上了頭尾說明。openssl命令行默認輸出的都是PEM格式的文件,要能夠在ios下使用,我們需要指定使用DER或者先生成PEM然後轉換稱DER。
使用openssl命令行生成密鑰對
1 | openssl req -x509 -out public_key.der -outform der -new -newkey rsa:1024 -keyout private_key.pem |
按照提示,填入私鑰的密碼,簽名證書的組織名、郵件等信息之後,就會生成包含有公鑰的證書文件public_key.der合私鑰文件private_key.pem。public_key.der文件用於分發到ios客戶端進行公鑰加解密,而private_key.pem文件留在服務器端供php使用。當然,如果爲了在服務器端進行加解密測試,那麼我們還可以生成一個服務器端PHP使用的pem公鑰文件:
1 | openssl rsa - in private_key.pem -pubout -out public_key.pem |
上面這個命令就會根據輸入的私鑰文件生成pem格式的公鑰文件了。從這裏也可以看到,根據私鑰,我們是可以生成相對應的公鑰的,這也就是爲什麼我們要把公鑰放在客戶端,而不是私鑰放在客戶端的原因了。
服務器端PHP的加解密函數
閒話不多說,貼一段代碼,肯定能看懂的了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | class RSAEncryptTest {
const PRIVATE_KEY = "-----BEGIN ENCRYPTED PRIVATE KEY----- MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI2aEhi35m/scCAggA MBQGCCqGSIb3DQMHBAihDOZw68gfKwSCAoAu2E/d2a9FgHDWoinhK2nMc2MlrgL+ kpWcZ5YyUEWUw87DFKrG7dkpAYOgLIpyDatXUVFy2EekZH0Iplqo+yswtho8NtpJ 7T7KJ0nbUXo4we658Ez0EHAnWw+xZegsmJGk2+5QRCALDFyYEIMp3UvqxBjPjfDM 1rEZ0j2o9U40ouDqUVxTpq7ZwHkx/EkB8xHwKpFexz8J0s6gjPy6yLUjX2ut63LD 6X4YPBQLCJIcaLZORoAQ01cxCaM+78WTLUjdhcaFvff9f1xkiUU3XrQQTpuM/3YH MQ6SMYDAgiOLqSCiMc0VABwf0/kdBnxu9/C/CK82ehA29cVAe8o7HgKg+WszCzTE +QRCJ2fa7nOd7UXzCDfKh5Hhq1RjLFocVK8OW7tIgW3ircltM1ow30FfEzIdvzmv LP0QhfGI3o9VT7r5qihGxtXtnGeUEGwvK0j0ozznfsNej7sVFP0Jfw39TdUlEENh OPjtuBBBHv/oafQ3jqYnrI4R12ZrEU0acm85vRJm32K1RT1ROMFpc5sU20S8nMGC I3iCzUlJPQF0t07bKexayvfWlJVAwEqBBCPTnvfTMBEt33iC72dQELbzMAM/n7th TcY/sReO/J4beGk3 //c7PImKIOcIvKF9Gp99l/+BM/LMZ7Thd/qwMOV6Eb3T4BvY ItC+P5Lr29XeINmLRHXKwr27uTxX0fwDpmpwkPbGreVXA2cCxHnEzkh2WP3qGa7q +Cwi03ISTEcZbNxLRGArtFUOIvNpz4+FS07OLWVKGl6K6bTffBx1tlZ492SqyNAC 7aP4/4I9Malnt0VjRKYPBCkTvVhoWBG+ThoOav5IV+w7ZDy8mtcrcAII ----- END ENCRYPTED PRIVATE KEY-----";
const PUBLIC_KEY = "-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAqjJs08oHvNdhlWC+kGBd90PD 7CVjClhRTk3nn+2NNaP4Bi5N/A18rdrV6clNAGUz4i/5q/VQXeLiGYYqgmAkKCJe gReMsfcnoOSWu+Tvxih/48pu1hwBrmMLFZPOOUWQ9YjQEo7SYBe0HKoEl6XMqNwz HV7sk9x6BKz9QeLi5QIDAQAB ----- END PUBLIC KEY-----";
private static $private_key ;
private static $public_key ;
public static function private_encrypt( $str ){
self::setup_key();
if (openssl_private_encrypt( $str , $encrypted , self:: $private_key ))
return $encrypted ;
}
public static function private_decrypt( $str ){
self::setup_key();
if (openssl_private_decrypt( $str , $decrypted , self:: $private_key ))
return $decrypted ;
}
public static function public_decrypt( $str ){
self::setup_key();
if (openssl_public_decrypt( $str , $decrypted , self:: $public_key ))
return $decrypted ;
}
public static function public_encrypt( $str ){
self::setup_key();
if (openssl_public_encrypt( $str , $encrypted , self:: $public_key ))
return $encrypted ;
}
private static function setup_key(){
if (!self:: $private_key ){
// 這裏的test就是在生成證書的時候設置的私鑰密碼
self:: $private_key = openssl_pkey_get_private(self::PRIVATE_KEY, "test" );
}
if (!self:: $public_key )
self:: $public_key = openssl_pkey_get_public(self::PUBLIC_KEY);
} } |
IOS客戶端的加解密
首先我們需要導入Security.framework,在ios中,我們主要關注四個函數
SecKeyEncrypt:使用公鑰對數據進行加密
SecKeyDecrypt:使用私鑰對數據進行解密
SecKeyRawVerify:使用公鑰對數字簽名和數據進行驗證,以確認該數據的來源合法性。什麼是數字簽名,可以參考百度百科這篇文章?
SecKeyRawSign:使用私鑰對數據進行摘要並生成數字簽名
從這幾個函數中,我們可以看到,我們使用公鑰能做的事情就有兩個:加密數據,以及對服務器端發來的數據進行簽名認證,但是如果你想跟我之前想的一樣,要使用公鑰來對數據進行解密,那就沒有自帶API了。如果想在服務器端使用私鑰加密數據,然後再在客戶端使用公鑰進行解密,以圖這樣來對交互數據進行加密,看來是行不通的。其實也應該是這樣,公鑰是公開的,因爲他可以編譯到二進制文本里面就認爲他不能被獲取其實是不對的。同時,RSA因爲都是做大數的運算,算法性能上比較差,如果做大數據量的加解密,對IOS來講,肯定也是不合適的。
這裏就把使用公鑰進行加密的代碼貼出來:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | // 我們在前面使用openssl生成的public_key.der文件的base64值,用你自己的替換掉這裏 #define RSA_KEY_BASE64 @"MIIC5DCCAk2gAwIBAgIJALUk4hrYth9oMA0GCSqGSIb3DQEBBQUAMIGKMQswCQYDVQQGEwJ\ DTjERMA8GA1UECAwIU2hhbmdoYWkxETAPBgNVBAcMCFNoYW5naGFpMQ4wDAYDVQQKDAVCYWl5aTEOMAwGA1UECwwFQmFpeWk\ xEDAOBgNVBAMMB1lvcmsuR3UxIzAhBgkqhkiG9w0BCQEWFGd5cTUzMTk5MjBAZ21haWwuY29tMB4XDTExMTAyNjAyNDUzMlo\ XDTExMTEyNTAyNDUzM1owgYoxCzAJBgNVBAYTAkNOMREwDwYDVQQIDAhTaGFuZ2hhaTERMA8GA1UEBwwIU2hhbmdoYWkxDjA\ MBgNVBAoMBUJhaXlpMQ4wDAYDVQQLDAVCYWl5aTEQMA4GA1UEAwwHWW9yay5HdTEjMCEGCSqGSIb3DQEJARYUZ3lxNTMxOTk\ yMEBnbWFpbC5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAK3cKya7oOi8jVMkRGVuNn/SiSS1y5knKLh6t98JukB\ DJZqo30LVPXXL9nHcYXBTulJgzutCOGQxw8ODfAKvXYxmX7QvLwlJRFEzrqzi3eAM2FYtZZeKbgV6PximOwCG6DqaFqd8X0W\ ezP1B2eWKz4kLIuSUKOmt0h3RpIPkatPBAgMBAAGjUDBOMB0GA1UdDgQWBBSIiLi2mehEgi/MwRZOld1mLlhl7TAfBgNVHSM\ EGDAWgBSIiLi2mehEgi/MwRZOld1mLlhl7TAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAB0GUsssoVEDs9vQxk0\ DzNr8pB0idfI+Farl46OZnW5ZwPu3dvSmhQ+yRdh7Ba54JCyvRy0JcWB+fZgO4QorNRbVVbBSuPg6wLzPuasy9TpmaaYaLLK\ Iena6Z60aFWRwhazd6+hIsKTMTExaWjndblEbhAsjdpg6QMsKurs9+izr" static SecKeyRef _public_key= nil ; + ( SecKeyRef ) getPublicKey{ // 從公鑰證書文件中獲取到公鑰的SecKeyRef指針
if (_public_key == nil ){
NSData *certificateData = [Base64 decode:RSA_KEY_BASE64];
SecCertificateRef 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);
}
_public_key = SecTrustCopyPublicKey(myTrust);
CFRelease (myCertificate);
CFRelease (myPolicy);
CFRelease (myTrust);
}
return _public_key; } + ( NSData *) rsaEncryptString:( NSString *) string{
SecKeyRef key = [ self getPublicKey];
size_t cipherBufferSize = SecKeyGetBlockSize(key);
uint8_t *cipherBuffer = malloc(cipherBufferSize * sizeof ( uint8_t ));
NSData *stringBytes = [string dataUsingEncoding: NSUTF8StringEncoding ];
size_t blockSize = cipherBufferSize - 11;
size_t blockCount = ( size_t )ceil([stringBytes length] / ( double )blockSize);
NSMutableData *encryptedData = [[[ NSMutableData alloc] init] autorelease];
for ( int i=0; i<blockCount; i++) {
int bufferSize = MIN(blockSize,[stringBytes length] - i * blockSize);
NSData *buffer = [stringBytes 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];
[encryptedData appendData:encryptedBytes];
[encryptedBytes release];
} else {
if (cipherBuffer) free(cipherBuffer);
return nil ;
}
}
if (cipherBuffer) free(cipherBuffer); // NSLog(@"Encrypted text (%d bytes): %@", [encryptedData length], [encryptedData description]); // NSLog(@"Encrypted text base64: %@", [Base64 encode:encryptedData]);
return encryptedData; } |
http://blog.yorkgu.me/2011/10/27/rsa-in-ios-using-publick-key-generated-by-openssl/