这已经难住了我-下面的代码使用海绵城堡的加密/解密Android -我试图实现跨平台的加密/解密iOS。
以下代码(来自Android)的工作方式,AES 128位CBC与pkcs7填充,使用提供的盐和密码,其中盐存储在mysql数据库中,密码由最终用户,以下代码改编自kelhoer的回答。
我使用AES128bit的原因是AES256在ios4 +中不可用,它是在iOS5+中引入的,并且不得不尝试使用openssl
来生成派生密钥和初始化向量(iv),据了解苹果拒绝与openssl库静态链接的应用程序是冒险的。
由于平台是基于iOS 4.2+,采用捆绑和静态链接openssl库似乎有些过火,最好使用CommonCryptor库。
这是带有海绵城堡代码的Android版本:
private static void encrypt(InputStream fin,
OutputStream fout,
String password,
byte[] bSalt) {
try {
PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(
new SHA256Digest()
);
char[] passwordChars = password.toCharArray();
final byte[] pkcs12PasswordBytes =
PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
pGen.init(pkcs12PasswordBytes, bSalt, ITERATIONS);
CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
ParametersWithIV aesCBCParams =
(ParametersWithIV) pGen.generateDerivedParameters(128, 128);
aesCBC.init(true, aesCBCParams);
PaddedBufferedBlockCipher aesCipher =
new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
aesCipher.init(true, aesCBCParams);
byte[] buf = new byte[BUF_SIZE];
// Read in the decrypted bytes and write the cleartext to out
int numRead = 0;
while ((numRead = fin.read(buf)) >= 0) {
if (numRead == 1024) {
byte[] plainTemp = new byte[
aesCipher.getUpdateOutputSize(numRead)];
int offset =
aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
final byte[] plain = new byte[offset];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
fout.write(plain, 0, plain.length);
} else {
byte[] plainTemp = new byte[aesCipher.getOutputSize(numRead)];
int offset =
aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
int last = aesCipher.doFinal(plainTemp, offset);
final byte[] plain = new byte[offset + last];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
fout.write(plain, 0, plain.length);
}
}
fout.close();
fin.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void decrypt(InputStream fin,
OutputStream fout,
String password,
byte[] bSalt) {
try {
PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(
new SHA256Digest()
);
char[] passwordChars = password.toCharArray();
final byte[] pkcs12PasswordBytes =
PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
pGen.init(pkcs12PasswordBytes, bSalt, ITERATIONS);
CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
ParametersWithIV aesCBCParams =
(ParametersWithIV) pGen.generateDerivedParameters(128, 128);
aesCBC.init(false, aesCBCParams);
PaddedBufferedBlockCipher aesCipher =
new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
aesCipher.init(false, aesCBCParams);
byte[] buf = new byte[BUF_SIZE];
// Read in the decrypted bytes and write the cleartext to out
int numRead = 0;
while ((numRead = fin.read(buf)) >= 0) {
if (numRead == 1024) {
byte[] plainTemp = new byte[
aesCipher.getUpdateOutputSize(numRead)];
int offset =
aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
// int last = aesCipher.doFinal(plainTemp, offset);
final byte[] plain = new byte[offset];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
fout.write(plain, 0, plain.length);
} else {
byte[] plainTemp = new byte[
aesCipher.getOutputSize(numRead)];
int offset =
aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
int last = aesCipher.doFinal(plainTemp, offset);
final byte[] plain = new byte[offset + last];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
fout.write(plain, 0, plain.length);
}
}
fout.close();
fin.close();
} catch (Exception e) {
e.printStackTrace();
}
}
然而,在iOS 4.2(与XCode一起工作)我不知道如何做等效的,
这是我在Objective C下尝试的,目的是从Android端解密数据,存储在mysql数据库中,以测试出来:
+(NSData*) decrypt:(NSData*)cipherData
userPassword:(NSString*)argPassword
genSalt:(NSData*)argPtrSalt{
size_t szPlainBufLen = cipherData.length + (kCCBlockSizeAES128);
uint8_t *ptrPlainBuf = malloc(szPlainBufLen);
//
const unsigned char *ptrPasswd =
(const unsigned char*)[argPassword
cStringUsingEncoding:NSASCIIStringEncoding];
int ptrPasswdLen = strlen(ptrPasswd);
//
NSString *ptrSaltStr = [[NSString alloc]
initWithData:argPtrSalt
encoding:NSASCIIStringEncoding];
const unsigned char *ptrSalt =
(const unsigned char *)[ptrSaltStr UTF8String];
NSString *ptrCipherStr =
[[NSString alloc]initWithData:cipherData
encoding:NSASCIIStringEncoding];
unsigned char *ptrCipher = (unsigned char *)[ptrCipherStr UTF8String];
unsigned char key[kCCKeySizeAES128];
unsigned char iv[kCCKeySizeAES128];
//
//int EVP_BytesToKey(const EVP_CIPHER *type,const EVP_MD *md,
//const unsigned char *salt, const unsigned char *data,
//int datal, int count, unsigned char *key,unsigned char *iv);
int i = EVP_BytesToKey(EVP_aes_128_cbc(),
EVP_sha256(),
ptrSalt,
ptrPasswd,
ptrPasswdLen,
PBKDF2_ITERATIONS,
key,
iv);
NSAssert(i == kCCKeySizeAES128,
@"Unable to generate key for AES");
//
size_t cipherLen = [cipherData length];
size_t outlength = 0;
//
CCCryptorStatus resultCCStatus = CCCrypt(kCCDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
key,
kCCBlockSizeAES128,
iv,
ptrCipher,
cipherLen,
ptrPlainBuf,
szPlainBufLen,
&outlength);
NSAssert(resultCCStatus == kCCSuccess,
@"Unable to perform PBE AES128bit decryption: %d", errno);
NSData *ns_dta_PlainData = nil;
if (resultCCStatus == kCCSuccess){
ns_dta_PlainData =
[NSData dataWithBytesNoCopy:ptrPlainBuf length:outlength];
}else{
return nil;
}
return ns_dta_PlainData;
}
已提供数据和用户密码,从CCCrypt
得到一个返回码为-4304
,表示解码不成功,解码失败。
我想也许编码方案会抛弃CommonCryptor的解密路由,因此转换为NSASCIIStringEncoding
的冗长方式。
Salt与密码数据一起存储,长度为32字节。
在这方面我错过了什么,记住,我对密码学很弱。
我冒昧地编写了Android端使用的PKCS12Parameters生成器的直接端口,该头文件的要点在上面。
实现也是直接复制,如这里所见,密码,被转换为PKCS12等效- unicode,大端,在末尾填充两个额外的零。
Generator 通过执行迭代次数生成派生密钥和iv,在本例中为1000,与Android端一样,使用SHA256 Digest,然后使用最终生成的密钥和iv作为CCCryptorCreate
的参数。
使用下面的代码示例也不能工作,它在调用CCCryptorFinal
-4304
结束代码摘录如下:
#define ITERATIONS 1000
PKCS12ParametersGenerator *pGen = [[PKCS12ParametersGenerator alloc]
init:argPassword
saltedHash:argPtrSalt
iterCount:ITERATIONS
keySize:128
initVectSize:128];
//
[pGen generateDerivedParameters];
//
CCCryptorRef decryptor = NULL;
// Create and Initialize the crypto reference.
CCCryptorStatus ccStatus = CCCryptorCreate(kCCDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
pGen.derivedKey.bytes,
kCCKeySizeAES128,
pGen.derivedIV.bytes,
&decryptor
);
NSAssert(ccStatus == kCCSuccess,
@"Unable to initialise decryptor!");
//
size_t szPlainBufLen = cipherData.length + (kCCBlockSizeAES128);
// Calculate byte block alignment for all calls through to and including final.
size_t szPtrPlainBufSize = CCCryptorGetOutputLength(decryptor, szPlainBufLen, true);
uint8_t *ptrPlainBuf = calloc(szPtrPlainBufSize, sizeof(uint8_t));
//
// Set up initial size.
size_t remainingBytes = szPtrPlainBufSize;
uint8_t *ptr = ptrPlainBuf;
size_t movedBytes = 0;
size_t totalBytesWritten = 0;
// Actually perform the encryption or decryption.
ccStatus = CCCryptorUpdate(decryptor,
(const void *) cipherData.bytes,
szPtrPlainBufSize,
ptr,
remainingBytes,
&movedBytes
);
NSAssert(ccStatus == kCCSuccess,
@"Unable to update decryptor! Error: %d", ccStatus);
ptr += movedBytes;
remainingBytes -= movedBytes;
totalBytesWritten += movedBytes;
//
// Finalize everything to the output buffer.
CCCryptorStatus resultCCStatus = CCCryptorFinal(decryptor,
ptr,
remainingBytes,
&movedBytes
);
totalBytesWritten += movedBytes;
if(decryptor) {
(void) CCCryptorRelease(decryptor);
decryptor = NULL;
}
NSAssert(resultCCStatus == kCCSuccess,
@"Unable to perform PBE AES128bit decryption: %d", resultCCStatus);
有趣的是,解密工作,对CCCryptorFinal
的最后调用返回0
,如果我在CCCryptorCreate
的开始用kCCOptionPKCS7Padding
代替0x0000
,即没有填充。唉,数据不是我所期望的,仍然完全打乱了,不管什么时候"不工作"。
它在某个地方失败了,所以如果有人有更好的想法,关于如何达到同等的效果,我很高兴听到其他的意见。
它要么改变Android端的机制,使其"跨平台"与iPhone兼容,要么以牺牲用于数据交换可移植性的平台上较弱的加密技术为代价,寻求另一种两端兼容的加密解决方案。
提供的输入数据:
- Base64编码的密码,盐和密码用':'分隔。
tnNhKyJ2vvrUzAmtQV5q9uEwzzAH63sTKtLf4pOQylw=:qTBluA+aNeFnEUfkUFUEVgNYrdz7enn5W1n4Q9uBKYmFfJeSCcbsfziErsa4EU9Cz/pO0KE4WE1QdqRcvSXthQ==
- 密码为
f00b4r
- 原字符串为
The quick brown fox jumped over the lazy dog and ran away
是的,我不得不放弃Android端的加密算法,这是一个挑战,找到一个跨平台兼容的。
我读了很多关于Rob Napier的RNCryptor的文章,然后在谷歌上搜索了一个Android版本,在那里我找到了JNCryptor,我冒险在iOS端使用了RNCryptor。
在github上分叉JNCryptor代码,以增加能够指定自定义设置的增强,并使用海绵城堡,用于旧版本的Android。从那时起,两个平台就可以互换加密/解密了。
我增强JNCryptor的原因是PKDBF2函数的迭代计数太高了——10000,这是默认值(因为代码将在较旧的手机上运行——它会崩溃——如果你有双核/四核的话就太好了!),并且需要重写迭代计数以使其更"可忍受"——1000。在RNCryptor中可以使用自定义设置。
感谢Rob Napier和Duncan Jones的工作!