iOS中的PBEWithMD5AndDES加密

时间:2011-08-22 20:08:25

标签: iphone objective-c ios encryption openssl

我在iOS中遇到PBEWithMD5AndDES加密问题。我使用这个https://gist.github.com/788840/24bc73ecd0ac3134cbd242892c74a06ac561d37b加密和解密字符串。

问题是我获得了不同的加密值,具体取决于我的方法所在的类。例如,我将所有加密方法移动到辅助类中并运行它。我注意到我得到了一个不同的加密值。

我现在在不同的类中有两个相同版本的相同方法,我并排运行它们。他们获得不同的加密值,而且无法解密其他人的加密值。我有点难过。

这是执行加密/解密的帮助程序类。

@implementation CryptoHelper

#pragma mark -
#pragma mark Init Methods
- (id)init
{
    if(self = [super init])
    {

    }
    return self;
}

#pragma mark -
#pragma mark String Specific Methods

/** 
 *  Encrypts a string for social blast service. 
 *  
 *  @param  plainString The string to encrypt;
 *
 *  @return NSString    The encrypted string. 
 */
- (NSString *)encryptString: (NSString *) plainString{

    // Convert string to data and encrypt
    NSData *data = [self encryptPBEWithMD5AndDESData:[plainString dataUsingEncoding:NSUTF8StringEncoding] password:@"1111"];



    // Get encrypted string from data
    return [data base64EncodingWithLineLength:1024];

}


/** 
 *  Descrypts a string from social blast service. 
 *  
 *  @param  plainString The string to decrypt;
 *
 *  @return NSString    The decrypted string. 
 */
- (NSString *)decryptString: (NSString *) encryptedString{

    // decrypt the data
    NSData * data = [self decryptPBEWithMD5AndDESData:[NSData dataWithBase64EncodedString:encryptedString] password:@"1111"];

    // extract and return string
    return [NSString stringWithUTF8String:[data bytes]];

}


#pragma mark -
#pragma mark Crypto Methods

- (NSData *)encryptPBEWithMD5AndDESData:(NSData *)inData password:(NSString *)password {
    return [self encodePBEWithMD5AndDESData:inData password:password direction:1];
}

- (NSData *)decryptPBEWithMD5AndDESData:(NSData *)inData password:(NSString *)password {
    return [self encodePBEWithMD5AndDESData:inData password:password direction:0];
}

- (NSData *)encodePBEWithMD5AndDESData:(NSData *)inData password:(NSString *)password direction:(int)direction
{
    NSLog(@"helper data = %@", inData);

    static const char gSalt[] =
    {
        (unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA,
        (unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA
    };

    unsigned char *salt = (unsigned char *)gSalt;
    int saltLen = strlen(gSalt);
    int iterations = 15;

    EVP_CIPHER_CTX cipherCtx;


    unsigned char *mResults; // allocated storage of results
    int mResultsLen = 0;

    const char *cPassword = [password UTF8String];

    unsigned char *mData = (unsigned char *)[inData bytes];
    int mDataLen = [inData length];


    SSLeay_add_all_algorithms();
    /*X509_ALGOR *algorithm = PKCS5_pbe_set(NID_pbeWithMD5AndDES_CBC,
                                          iterations, salt, saltLen);*/
        const EVP_CIPHER *cipher = EVP_des_cbc();

    // Need to set with iv
    X509_ALGOR *algorithm = PKCS5_pbe2_set_iv(cipher, iterations, 
                                          salt, saltLen, salt, NID_hmacWithMD5);


    memset(&cipherCtx, 0, sizeof(cipherCtx));

    if (algorithm != NULL)
    {
        EVP_CIPHER_CTX_init(&(cipherCtx));



        if (EVP_PBE_CipherInit(algorithm->algorithm, cPassword, strlen(cPassword),
                               algorithm->parameter, &(cipherCtx), direction))
        {

            EVP_CIPHER_CTX_set_padding(&cipherCtx, 1);

            int blockSize = EVP_CIPHER_CTX_block_size(&cipherCtx);
            int allocLen = mDataLen + blockSize + 1; // plus 1 for null terminator on decrypt
            mResults = (unsigned char *)OPENSSL_malloc(allocLen);


            unsigned char *in_bytes = mData;
            int inLen = mDataLen;
            unsigned char *out_bytes = mResults;
            int outLen = 0;



            int outLenPart1 = 0;
            if (EVP_CipherUpdate(&(cipherCtx), out_bytes, &outLenPart1, in_bytes, inLen))
            {
                out_bytes += outLenPart1;
                int outLenPart2 = 0;
                if (EVP_CipherFinal(&(cipherCtx), out_bytes, &outLenPart2))
                {
                    outLen += outLenPart1 + outLenPart2;
                    mResults[outLen] = 0;
                    mResultsLen = outLen;
                }
            } else {
                unsigned long err = ERR_get_error();

                ERR_load_crypto_strings();
                ERR_load_ERR_strings();
                char errbuff[256];
                errbuff[0] = 0;
                ERR_error_string_n(err, errbuff, sizeof(errbuff));
                NSLog(@"OpenSLL ERROR:\n\tlib:%s\n\tfunction:%s\n\treason:%s\n",
                      ERR_lib_error_string(err),
                      ERR_func_error_string(err),
                      ERR_reason_error_string(err));
                ERR_free_strings();
            }


            NSData *encryptedData = [NSData dataWithBytes:mResults length:mResultsLen]; //(NSData *)encr_buf;


            //NSLog(@"encryption result: %@\n", [encryptedData base64EncodingWithLineLength:1024]);

            EVP_cleanup();

            return encryptedData;
        }
    }
    EVP_cleanup();
    return nil;

}

@end

我正在尝试复制此java函数的结果。我有同样的盐。

public DesEncrypter(String passPhrase) {
    try {
        // Create the key
        KeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray(), salt, iterationCount);
        SecretKey key = SecretKeyFactory.getInstance(
            "PBEWithMD5AndDES").generateSecret(keySpec);
        ecipher = Cipher.getInstance(key.getAlgorithm());
        dcipher = Cipher.getInstance(key.getAlgorithm());

        // Prepare the parameter to the ciphers
        AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount);

        // Create the ciphers
        ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
        dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
    } catch (java.security.InvalidAlgorithmParameterException e) {
    } catch (java.security.spec.InvalidKeySpecException e) {
    } catch (javax.crypto.NoSuchPaddingException e) {
    } catch (java.security.NoSuchAlgorithmException e) {
    } catch (java.security.InvalidKeyException e) {
    }
}

6 个答案:

答案 0 :(得分:6)

接受的答案似乎是使用未包含在iOS SDK中的OpenSSL。这是一个加密和解密解决方案,它使用包含的CommonCrypto库(在libSystem中)。我有点像ObjC n00b,所以请把这段代码带上一粒盐。顺便说一句,这段代码将成功解密使用java的PBEWithMD5AndDES密码加密的数据。希望这会为其他人节省一两天。

#include <CommonCrypto/CommonDigest.h>
#include <CommonCrypto/CommonCryptor.h>


+(NSData*) cryptPBEWithMD5AndDES:(CCOperation)op usingData:(NSData*)data withPassword:(NSString*)password andSalt:(NSData*)salt andIterating:(int)numIterations {
    unsigned char md5[CC_MD5_DIGEST_LENGTH];
    memset(md5, 0, CC_MD5_DIGEST_LENGTH);
    NSData* passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];

    CC_MD5_CTX ctx;
    CC_MD5_Init(&ctx);
    CC_MD5_Update(&ctx, [passwordData bytes], [passwordData length]);
    CC_MD5_Update(&ctx, [salt bytes], [salt length]);
    CC_MD5_Final(md5, &ctx);

    for (int i=1; i<numIterations; i++) {
        CC_MD5(md5, CC_MD5_DIGEST_LENGTH, md5);
    }

    size_t cryptoResultDataBufferSize = [data length] + kCCBlockSizeDES;
    unsigned char cryptoResultDataBuffer[cryptoResultDataBufferSize];
    size_t dataMoved = 0;

    unsigned char iv[kCCBlockSizeDES];
    memcpy(iv, md5 + (CC_MD5_DIGEST_LENGTH/2), sizeof(iv)); //iv is the second half of the MD5 from building the key

    CCCryptorStatus status =
        CCCrypt(op, kCCAlgorithmDES, kCCOptionPKCS7Padding, md5, (CC_MD5_DIGEST_LENGTH/2), iv, [data bytes], [data length],
            cryptoResultDataBuffer, cryptoResultDataBufferSize, &dataMoved);

    if(0 == status) {
        return [NSData dataWithBytes:cryptoResultDataBuffer length:dataMoved];
    } else {
        return NULL;
    }
}

答案 1 :(得分:3)

问题在于您没有为加密指定IV,这样会自动为您生成随机数,这也是您每次获得不同结果的原因。

尝试使用PKCS5_pbe2_set_iv代替PKCS5_pbe2_set提供明确的IV值,您可以随机选择,就像您的盐值一样。

答案 2 :(得分:3)

使用johwayner的响应,为了解释来自 jasypt 的Java的 PBEWithMD5AndDES ,我解决了一些问题。这就是我最终的结果:

@interface NSData (PBEEncryption)

/**
 * Decrypt the receiver using PKCS#5 PBE with MD5 and DES assuming that the salt is prefixed into the first 8 bytes of
 * the data and the number of key obtention iterations is 1000. This is compatible with Java's PBEWithMD5AndDES
 * encryption when the encryption generates a random salt for the data (the default when providing no salt).
 */
- (NSData *)decrytpPBEWithMD5AndDESUsingPassword:(NSData *)password;

/**
 * Decrypt the receiver using PKCS#5 PBE with MD5 and DES. Explicitly provide the salt and number of key obtention
 * iterations.
 */
- (NSData *)decrytpPBEWithMD5AndDESUsingPassword:(NSData *)password salt:(NSData *)salt iterations:(NSUInteger)iterations;

@end


@implementation NSData (PBEEncryption)

- (NSData *)decrytpPBEWithMD5AndDESUsingPassword:(NSData *)password {
    NSData *salt = nil;
    NSData *input = self;
    if ([input length] > 8) {
        salt = [input subdataWithRange:NSMakeRange(0, 8)];
        input = [input subdataWithRange:NSMakeRange(8, [input length] - 8)];
    }
    return [input decrytpPBEWithMD5AndDESUsingPassword:password salt:salt iterations:1000];
}

- (NSData *)decrytpPBEWithMD5AndDESUsingPassword:(NSData *)password salt:(NSData *)salt iterations:(NSUInteger)iterations {
    unsigned char md5[CC_MD5_DIGEST_LENGTH] = {};

    CC_MD5_CTX ctx;
    CC_MD5_Init(&ctx);
    CC_MD5_Update(&ctx, [password bytes], [password length]);
    CC_MD5_Update(&ctx, [salt bytes], [salt length]);
    CC_MD5_Final(md5, &ctx);

    for (NSUInteger i = 1; i < iterations; i++) {
        CC_MD5(md5, CC_MD5_DIGEST_LENGTH, md5);
    }

    // initialization vector is the second half of the MD5 from building the key
    unsigned char iv[kCCBlockSizeDES];
    assert(kCCBlockSizeDES == CC_MD5_DIGEST_LENGTH / 2);
    memcpy(iv, md5 + kCCBlockSizeDES, sizeof(iv));

    NSMutableData *output = [NSMutableData dataWithLength:([self length] + kCCBlockSize3DES)];
    size_t outputLength = 0;
    CCCryptorStatus status =
        CCCrypt(kCCDecrypt, kCCAlgorithmDES, kCCOptionPKCS7Padding, md5, kCCBlockSizeDES, iv,
                [self bytes], [self length], [output mutableBytes], [output length], &outputLength);

    if (status == kCCSuccess) { [output setLength:outputLength]; }
    else { output = nil; }

    return output;
}

@end

像这样使用:

NSString *password = @"myTopSecretPassword";
NSData *inputData = [NSData dataFromBase64String:@"base64string"];
NSData *outputData = [inputData decrytpPBEWithMD5AndDESUsingPassword:
                      [password dataUsingEncoding:NSUTF8StringEncoding]];

答案 3 :(得分:2)

不确定这里有什么协议来接受答案/提出答案。如果我做错了,我道歉。答案结果证明盐中没有最后一个字节。我实际上不需要使用3DES加密的IV。我赞成了另一个答案,因为它有助于更​​多地了解加密。

这是最终目标c类。

@implementation CryptoHelper

#pragma mark -
#pragma mark Init Methods
- (id)init
{
    if(self = [super init])
    {

    }
    return self;
}

#pragma mark -
#pragma mark String Specific Methods

/** 
 *  Encrypts a string for social blast service. 
 *  
 *  @param  plainString The string to encrypt;
 *
 *  @return NSString    The encrypted string. 
 */
- (NSString *)encryptString: (NSString *) plainString{

    // Convert string to data and encrypt
    NSData *data = [self encryptPBEWithMD5AndDESData:[plainString dataUsingEncoding:NSUTF8StringEncoding] password:@"1111"];



    // Get encrypted string from data
    return [data base64EncodingWithLineLength:1024];

}


/** 
 *  Descrypts a string from social blast service. 
 *  
 *  @param  plainString The string to decrypt;
 *
 *  @return NSString    The decrypted string. 
 */
- (NSString *)decryptString: (NSString *) encryptedString{

    // decrypt the data
    NSData * data = [self decryptPBEWithMD5AndDESData:[NSData dataWithBase64EncodedString:encryptedString] password:@"1111"];

    // extract and return string
    return [NSString stringWithUTF8String:[data bytes]];

}


#pragma mark -
#pragma mark Crypto Methods

- (NSData *)encryptPBEWithMD5AndDESData:(NSData *)inData password:(NSString *)password {
    return [self encodePBEWithMD5AndDESData:inData password:password direction:1];
}

- (NSData *)decryptPBEWithMD5AndDESData:(NSData *)inData password:(NSString *)password {
    return [self encodePBEWithMD5AndDESData:inData password:password direction:0];
}

- (NSData *)encodePBEWithMD5AndDESData:(NSData *)inData password:(NSString *)password direction:(int)direction
{
    NSLog(@"helper data = %@", inData);

    static const char gSalt[] =
    {
        (unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA,
        (unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA,
        (unsigned char)0x00
    };

    unsigned char *salt = (unsigned char *)gSalt;
    int saltLen = strlen(gSalt);
    int iterations = 15;

    EVP_CIPHER_CTX cipherCtx;


    unsigned char *mResults; // allocated storage of results
    int mResultsLen = 0;

    const char *cPassword = [password UTF8String];

    unsigned char *mData = (unsigned char *)[inData bytes];
    int mDataLen = [inData length];


    SSLeay_add_all_algorithms();
    X509_ALGOR *algorithm = PKCS5_pbe_set(NID_pbeWithMD5AndDES_CBC,
                                          iterations, salt, saltLen);



    memset(&cipherCtx, 0, sizeof(cipherCtx));

    if (algorithm != NULL)
    {
        EVP_CIPHER_CTX_init(&(cipherCtx));



        if (EVP_PBE_CipherInit(algorithm->algorithm, cPassword, strlen(cPassword),
                               algorithm->parameter, &(cipherCtx), direction))
        {

            EVP_CIPHER_CTX_set_padding(&cipherCtx, 1);

            int blockSize = EVP_CIPHER_CTX_block_size(&cipherCtx);
            int allocLen = mDataLen + blockSize + 1; // plus 1 for null terminator on decrypt
            mResults = (unsigned char *)OPENSSL_malloc(allocLen);


            unsigned char *in_bytes = mData;
            int inLen = mDataLen;
            unsigned char *out_bytes = mResults;
            int outLen = 0;



            int outLenPart1 = 0;
            if (EVP_CipherUpdate(&(cipherCtx), out_bytes, &outLenPart1, in_bytes, inLen))
            {
                out_bytes += outLenPart1;
                int outLenPart2 = 0;
                if (EVP_CipherFinal(&(cipherCtx), out_bytes, &outLenPart2))
                {
                    outLen += outLenPart1 + outLenPart2;
                    mResults[outLen] = 0;
                    mResultsLen = outLen;
                }
            } else {
                unsigned long err = ERR_get_error();

                ERR_load_crypto_strings();
                ERR_load_ERR_strings();
                char errbuff[256];
                errbuff[0] = 0;
                ERR_error_string_n(err, errbuff, sizeof(errbuff));
                NSLog(@"OpenSLL ERROR:\n\tlib:%s\n\tfunction:%s\n\treason:%s\n",
                      ERR_lib_error_string(err),
                      ERR_func_error_string(err),
                      ERR_reason_error_string(err));
                ERR_free_strings();
            }


            NSData *encryptedData = [NSData dataWithBytes:mResults length:mResultsLen]; //(NSData *)encr_buf;


            //NSLog(@"encryption result: %@\n", [encryptedData base64EncodingWithLineLength:1024]);

            EVP_cleanup();

            return encryptedData;
        }
    }
    EVP_cleanup();
    return nil;

}

@end

答案 4 :(得分:0)

我要感谢wbyoung。他的回答帮助我解决了如何从iOS加密数据并将其从Java解密的谜团。以下是我对他的代码所做的补充。

诀窍是使用用于加密数据的salt,并将其添加到结果中。然后,Java将能够对其进行解密。

@implementation NSData (PBEEncryption)

- (NSData *)encryptPBEWithMD5AndDESUsingPassword:(NSData *)password {
    unsigned char gSalt[] =
    {
        (unsigned char)0x18, (unsigned char)0x79, (unsigned char)0x6D, (unsigned char)0x6D,
        (unsigned char)0x35, (unsigned char)0x3A, (unsigned char)0x6A, (unsigned char)0x60,
        (unsigned char)0x00
    };

    NSData *salt = nil;
    salt = [NSData dataWithBytes:gSalt length:strlen(gSalt)];

    NSData* encrypted = [self encryptPBEWithMD5AndDESUsingPassword:password salt:salt iterations:1000];
    NSMutableData* result = [NSMutableData dataWithData:salt];
    [result appendData:encrypted];
    return [NSData dataWithData:result];

}

- (NSData *)encryptPBEWithMD5AndDESUsingPassword:(NSData *)password salt:(NSData *)salt iterations:(NSUInteger)iterations {
    unsigned char md5[CC_MD5_DIGEST_LENGTH] = {};

    CC_MD5_CTX ctx;
    CC_MD5_Init(&ctx);
    CC_MD5_Update(&ctx, [password bytes], [password length]);
    CC_MD5_Update(&ctx, [salt bytes], [salt length]);
    CC_MD5_Final(md5, &ctx);

    for (NSUInteger i = 1; i < iterations; i++) {
        CC_MD5(md5, CC_MD5_DIGEST_LENGTH, md5);
    }

    // initialization vector is the second half of the MD5 from building the key
    unsigned char iv[kCCBlockSizeDES];
    assert(kCCBlockSizeDES == CC_MD5_DIGEST_LENGTH / 2);
    memcpy(iv, md5 + kCCBlockSizeDES, sizeof(iv));

    NSMutableData *output = [NSMutableData dataWithLength:([self length] + kCCBlockSize3DES)];
    size_t outputLength = 0;
    CCCryptorStatus status =
    CCCrypt(kCCEncrypt, kCCAlgorithmDES, kCCOptionPKCS7Padding, md5, kCCBlockSizeDES, iv,
            [self bytes], [self length], [output mutableBytes], [output length], &outputLength);

    if (status == kCCSuccess) { [output setLength:outputLength]; }
    else { output = nil; }

    return output;
}


@end

加密字符串的相应Swift代码:

static func encryptForOverTheWire(string: String) -> String {
    let stringData = string.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
    let passwordData = PASSWORD.dataUsingEncoding(NSUTF8StringEncoding)
    let encryptedData = stringData?.encryptPBEWithMD5AndDESUsingPassword(passwordData)
    let base64 = encryptedData?.base64EncodedDataWithOptions(NSDataBase64EncodingOptions())

    guard base64 != nil else { return "" }

    let result = String(data: base64!, encoding: NSUTF8StringEncoding)
    return result ?? ""
}

答案 5 :(得分:0)

通过这种方式,我可以使其与Swift一起使用。我面临的唯一问题是我从目标C模块中获取了一个Data对象,但是当我尝试转换为String时,它的值为nil。这是代码。请让我知道我在这里做错了什么

static func encrypt(inString: String) -> String? {

    let passwordStr = "blablalba"
    let iterations:Int32 = 19
    let salt = [0x44,  0x44,  0x22,  0x22,  0x56,  0x35,  0xE3, 0x03] as [UInt8]
    let operation: CCOperation = UInt32(kCCEncrypt)
    let saltData = NSData(bytes: salt, length: salt.count)

    let out = CryptoHelper.cryptPBE(withMD5AndDES: operation, using: inString.data(using: .utf8), withPassword: passwordStr, andSalt: saltData as Data, andIterating: iterations) as Data

    let outString = String.init(data: out, encoding: .utf8)

    return outString
}