公司接口需要加密,因此需要iOS或android端、.NET服务器端实现同样的加密解密方式。
原本在项目中有DES加密方式的实现,用于保存账号密码以便App下次打开时自动登录,加密解密在本地都无问题。但是使用同样的加密方法传输到服务器,服务器居然无法还原原始字符串。
以下是iOS端加密和解密的方法:
static Byte iv[] = {1,2,3,4,5,6,7,8};
+ (NSString *) encryptUseDES:(NSString *)plainText key:(NSString *)key
{
NSString *ciphertext = nil;
const char *textBytes = [plainText UTF8String];
NSUInteger dataLength = [plainText length];
unsigned char buffer[1024];
memset(buffer, 0, sizeof(char));
size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmDES,
kCCOptionPKCS7Padding,
[key UTF8String], kCCKeySizeDES,
iv,
textBytes,
dataLength,
buffer, 1024,
&numBytesEncrypted);
if (cryptStatus == kCCSuccess) {
NSData *data = [NSData dataWithBytes:buffer length:(NSUInteger)numBytesEncrypted];
ciphertext =[GTMBase64 stringByEncodingData:data];
}
return ciphertext;
}
+ (NSString*)decryptUseDES:(NSString*)cipherText key:(NSString*)key {
NSData* cipherData = [GTMBase64 decodeString:cipherText];
unsigned char buffer[1024];
memset(buffer, 0, sizeof(char));
size_t numBytesDecrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
kCCAlgorithmDES,
kCCOptionPKCS7Padding,
[key UTF8String],
kCCKeySizeDES,
iv,
[cipherData bytes],
[cipherData length],
buffer,
1024,
&numBytesDecrypted);
NSString* plainText = nil;
if (cryptStatus == kCCSuccess) {
NSData* data = [NSData dataWithBytes:buffer length:(NSUInteger)numBytesDecrypted];
plainText = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
return plainText;
}
</br>
于是开始走上查找原因的路。
首先,由于本地的加密和解密正常能用,怀疑可能是服务器解密函数的问题,于是让服务器进行加密解密验证,发现没有问题。
然后对比两边的加密结果,对同一字符串使用同一个key加密,服务器的值为aDcUJMile6I=
,而iOS端的值是 CHgXZ9ukWr0=
,值不同但是比较相似。
然后查看OC和C#的实现,详细比对加密函数的各项参数,终于发现了原因所在。DES参数包括工作模式(包括电子密码本ECB、加密分组链接CBC、加密反馈CFB和OFB四种模式)、填充模式、加密密钥、初始化向量、字符串编码格式等。
问题的原因就在于初始化向量的不同。
下面分析一下两端实现中使用的参数。
服务器的C#代码类似于:
public string Encrypt(string pToEncrypt, string sKey)
{
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
byte[] inputByteArray = Encoding.Default.GetBytes(pToEncrypt);
des.Key = ASCIIEncoding.ASCII.GetBytes(sKey);
des.IV = ASCIIEncoding.ASCII.GetBytes(sKey);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(),CryptoStreamMode.Write);
cs.Write(inputByteArray, 0, inputByteArray.Length);
cs.FlushFinalBlock();
StringBuilder ret = new StringBuilder();
foreach(byte b in ms.ToArray())
{
ret.AppendFormat("{0:X2}", b);
}
ret.ToString();
return ret.ToString();
}
和我本地加密参数相比:编码格式都采用UTF8编码,工作模式一致,填充模式都采用 PKCS7方式,加密密钥也一致,但是初始向量IV不一样,我本地使用了自定义的字符,而服务器使用密钥key。
于是,将本地代码修改,使用密钥key作为偏移量。再次测试时服务器终于可以正确解密了。
简而言之,不同平台协同工作时,采用原理一致的方法和一致的参数非常重要,可以通过查看源代码来确保这一点。在DES加密中,要着重确定5个方面的参数是否一致,包括:编码格式、工作模式、填充模式、加密密钥、初始向量。
</br>
附加修改后的密解和密方法:
+(NSString *)encryptUseDES:(NSString *)plainText key:(NSString *)key{
NSString *ciphertext = nil;
const char *textBytes = [plainText UTF8String];
NSUInteger dataLength = [plainText length];
unsigned char buffer[1024];
memset(buffer, 0, sizeof(char));
size_t numBytesEncrypted = 0;
const void *iv = [key UTF8String];
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmDES,
kCCOptionPKCS7Padding,
[key UTF8String], kCCKeySizeDES,
iv,
textBytes,
dataLength,
buffer, 1024,
&numBytesEncrypted);
if (cryptStatus == kCCSuccess) {
NSData *data = [NSData dataWithBytes:buffer length:(NSUInteger)numBytesEncrypted];
ciphertext = [[NSString alloc] initWithData:[GTMBase64 encodeData:data] encoding:NSUTF8StringEncoding];
}
return ciphertext;
}
+ (NSString*)decryptUseDES:(NSString*)cipherText key:(NSString*)key {
NSData* cipherData = [GTMBase64 decodeString:cipherText];
unsigned char buffer[1024];
memset(buffer, 0, sizeof(char));
size_t numBytesDecrypted = 0;
const void *iv = [key UTF8String];
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
kCCAlgorithmDES,
kCCOptionPKCS7Padding,
[key UTF8String],
kCCKeySizeDES,
iv,
[cipherData bytes],
[cipherData length],
buffer,
1024,
&numBytesDecrypted);
NSString* plainText = nil;
if (cryptStatus == kCCSuccess) {
NSData* data = [NSData dataWithBytes:buffer length:(NSUInteger)numBytesDecrypted];
plainText = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
return plainText;
}