/* NSData+Crypto.m - this file is part of SOGo * * Copyright (C) 2012, 2020 Nicolas Höft * Copyright (C) 2012-2020 Inverse inc. * Copyright (C) 2012 Jeroen Dekkers * * Author: Nicolas Höft * Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #define _XOPEN_SOURCE 600 #include #include #if !defined(__OpenBSD__) && !defined(__FreeBSD__) #include #endif #if defined(HAVE_GNUTLS) #include #include #include "md4.h" #define MD4_DIGEST_LENGTH 16 #define MD5_DIGEST_LENGTH 16 #define SHA_DIGEST_LENGTH 20 #define SHA256_DIGEST_LENGTH 32 #define SHA512_DIGEST_LENGTH 64 #elif defined(HAVE_OPENSSL) #include #include #include #include #else #error this module requires either gnutls or openssl #endif #ifdef HAVE_SODIUM #include #endif #include "aes.h" #include "crypt_blowfish.h" #include "lmhash.h" #include "pkcs5_pbkdf2.h" #import #import #import "NSData+Crypto.h" static unsigned charTo4Bits(char c); #if defined(HAVE_GNUTLS) static BOOL check_gnutls_init(void); static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input); #endif #define BLF_CRYPT_DEFAULT_COMPLEXITY (5) #define BLF_CRYPT_SALT_LEN (16) #define BLF_CRYPT_BUFFER_LEN (128) #define BLF_CRYPT_PREFIX_LEN (7+22+1) /* $2.$nn$ + salt */ #define BLF_CRYPT_PREFIX "$2y" static const char salt_chars[] = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; @implementation NSData (SOGoCryptoExtension) /** * Covert binary data to hex encoded data (lower-case). * * @param theData The NSData to be converted into a hex-encoded string. * @return Hex-Encoded data */ + (NSString *) encodeDataAsHexString: (NSData *) theData { unsigned int byteLength = [theData length], byteCounter = 0; unsigned int stringLength = (byteLength * 2) + 1, stringCounter = 0; unsigned char dstBuffer[stringLength]; unsigned char srcBuffer[byteLength]; unsigned char *srcPtr = srcBuffer; [theData getBytes: srcBuffer]; const unsigned char t[16] = "0123456789abcdef"; for (; byteCounter < byteLength; byteCounter++) { unsigned src = *srcPtr; dstBuffer[stringCounter++] = t[src >> 4]; dstBuffer[stringCounter++] = t[src & 15]; srcPtr++; } dstBuffer[stringCounter] = '\0'; return [NSString stringWithUTF8String: (char*)dstBuffer]; } /** * Covert hex-encoded data to binary data. * * @param theString The hex-encoded string to be converted into binary data (works both for upper and lowe case characters) * @return binary data or nil if unsuccessful */ + (NSData *) decodeDataFromHexString: (NSString *) theString { unsigned int stringLength = [theString length]; unsigned int byteLength = stringLength/2; unsigned int byteCounter = 0; unsigned char srcBuffer[stringLength]; [theString getCString:(char *)srcBuffer]; unsigned char *srcPtr = srcBuffer; unsigned char dstBuffer[byteLength]; unsigned char *dst = dstBuffer; while (byteCounter < byteLength) { unsigned char c = *srcPtr++; unsigned char d = *srcPtr++; unsigned hi = 0, lo = 0; hi = charTo4Bits(c); lo = charTo4Bits(d); if (hi == 255 || lo == 255) { //errorCase return nil; } dstBuffer[byteCounter++] = ((hi << 4) | lo); } return [NSData dataWithBytes: dst length: byteLength]; } /** * Generate a binary key which can be used for salting hashes. * * @param theLength length of the binary data to be generated in bytes * @return Pseudo-random binary data with length theLength or nil, if an error occured */ + (NSData *) generateSaltForLength: (unsigned int) theLength { return [NSData generateSaltForLength: theLength withPrintable: NO]; } /** * Generate a binary key which can be used for salting hashes. When using * with doBase64 == YES then the data will be longer than theLength * * @param theLength Length of the binary data to be generated in bytes * @param doPrintable Use only printable characters * @return Pseudo-random binary data with length theLength or nil, if an error occured */ + (NSData *) generateSaltForLength: (unsigned int) theLength withPrintable: (BOOL) doPrintable { char *buf; int fd; NSData *data; unsigned int i; fd = open("/dev/urandom", O_RDONLY); if (fd > 0) { buf = (char *)malloc(theLength); read(fd, buf, theLength); close(fd); if (doPrintable == YES) { for (i = 0; i < theLength; i++) { buf[i] = salt_chars[buf[i] % (sizeof(salt_chars)-1)]; } } data = [NSData dataWithBytesNoCopy: buf length: theLength freeWhenDone: YES]; return data; } return nil; } /** * Encrypt/Hash the data with a given scheme * * @param passwordScheme The scheme to use for hashing/encryption. * @param theSalt The salt to be used. If none is given but needed, it will be generated * @return Binary data from the encryption by the specified scheme. On error the function returns nil. */ - (NSData *) asCryptedPassUsingScheme: (NSString *) passwordScheme withSalt: (NSData *) theSalt keyPath: (NSString *) theKeyPath { if ([passwordScheme caseInsensitiveCompare: @"none"] == NSOrderedSame || [passwordScheme caseInsensitiveCompare: @"plain"] == NSOrderedSame || [passwordScheme caseInsensitiveCompare: @"cleartext"] == NSOrderedSame) { return self; } else if ([passwordScheme caseInsensitiveCompare: @"crypt"] == NSOrderedSame) { return [self asCryptUsingSalt: theSalt]; } else if ([passwordScheme caseInsensitiveCompare: @"md5-crypt"] == NSOrderedSame) { return [self asMD5CryptUsingSalt: theSalt]; } else if ([passwordScheme caseInsensitiveCompare: @"md4"] == NSOrderedSame) { return [self asMD4]; } else if ([passwordScheme caseInsensitiveCompare: @"md5"] == NSOrderedSame || [passwordScheme caseInsensitiveCompare: @"plain-md5"] == NSOrderedSame || [passwordScheme caseInsensitiveCompare: @"ldap-md5"] == NSOrderedSame) { return [self asMD5]; } else if ([passwordScheme caseInsensitiveCompare: @"cram-md5"] == NSOrderedSame) { return [self asCramMD5]; } else if ([passwordScheme caseInsensitiveCompare: @"smd5"] == NSOrderedSame) { return [self asSMD5UsingSalt: theSalt]; } else if ([passwordScheme caseInsensitiveCompare: @"sha"] == NSOrderedSame) { return [self asSHA1]; } else if ([passwordScheme caseInsensitiveCompare: @"ssha"] == NSOrderedSame) { return [self asSSHAUsingSalt: theSalt]; } else if ([passwordScheme caseInsensitiveCompare: @"sha256"] == NSOrderedSame) { return [self asSHA256]; } else if ([passwordScheme caseInsensitiveCompare: @"ssha256"] == NSOrderedSame) { return [self asSSHA256UsingSalt: theSalt]; } else if ([passwordScheme caseInsensitiveCompare: @"sha512"] == NSOrderedSame) { return [self asSHA512]; } else if ([passwordScheme caseInsensitiveCompare: @"ssha512"] == NSOrderedSame) { return [self asSSHA512UsingSalt: theSalt]; } else if ([passwordScheme caseInsensitiveCompare: @"sha256-crypt"] == NSOrderedSame) { return [self asSHA256CryptUsingSalt: theSalt]; } else if ([passwordScheme caseInsensitiveCompare: @"sha512-crypt"] == NSOrderedSame) { return [self asSHA512CryptUsingSalt: theSalt]; } else if ([passwordScheme caseInsensitiveCompare: @"blf-crypt"] == NSOrderedSame) { return [self asBlowfishCryptUsingSalt: theSalt]; } else if ([passwordScheme caseInsensitiveCompare: @"pbkdf2"] == NSOrderedSame) { return [self asPBKDF2SHA1UsingSalt: theSalt]; } #ifdef HAVE_SODIUM else if ([passwordScheme caseInsensitiveCompare: @"argon2i"] == NSOrderedSame) { return [self asArgon2iUsingSalt: theSalt]; } # ifdef crypto_pwhash_ALG_ARGON2ID13 else if ([passwordScheme caseInsensitiveCompare: @"argon2id"] == NSOrderedSame) { return [self asArgon2idUsingSalt: theSalt]; } # endif /* crypto_pwhash_ALG_ARGON2ID13 */ #endif /* HAVE_SODIUM */ else if ([[passwordScheme lowercaseString] hasPrefix: @"sym"]) { // We first support one sym cipher, AES-128-CBC. If something else is provided // we return nil for now. Example of what theSalt might contain: // $AES-128-CBC$cinlbHKnyBApySphVCz6yA==$Z9hjCXfMhz4xbXkW+aMkAw== // If theSalt is empty, that means we are not validating a password // but rather changing it. In this case, we generate an IV. NSString *cipher, *iv; cipher = nil; iv = nil; if ([theSalt length]) { NSString *s; NSArray *a; s = [[NSString alloc] initWithData: theSalt encoding: NSUTF8StringEncoding]; [s autorelease]; a = [s componentsSeparatedByString: @"$"]; cipher = [a objectAtIndex: 1]; iv = [a objectAtIndex: 2]; } else { if ([passwordScheme caseInsensitiveCompare: @"sym-aes-128-cbc"] == NSOrderedSame) cipher = @"AES-128-CBC"; } if ([cipher caseInsensitiveCompare: @"AES-128-CBC"] == NSOrderedSame) return [self asSymAES128CBCUsingIV: iv keyPath: theKeyPath]; } // in case the scheme was not detected, return nil return nil; } /** * Verify the given password data is equivalent with the * clear text password using the passed encryption scheme * * @param passwordScheme The password scheme to use for comparison * @param thePassword cleartext key */ - (BOOL) verifyUsingScheme: (NSString *) passwordScheme withPassword: (NSData *) thePassword keyPath: (NSString *) theKeyPath { NSData *passwordCrypted; NSData *salt; salt = [self extractSalt: passwordScheme]; if (salt == nil) return NO; #ifdef HAVE_SODIUM // use verification function provided by libsodium if ([passwordScheme caseInsensitiveCompare: @"argon2i"] == NSOrderedSame #ifdef crypto_pwhash_ALG_ARGON2ID13 || [passwordScheme caseInsensitiveCompare: @"argon2id"] == NSOrderedSame #endif /* crypto_pwhash_ALG_ARGON2ID13 */ ) { NSString *cryptString; int result; if (sodium_init() < 0) return NO; // For the sodium comparison we need to pass a null-terminated string // as the first parameter cryptString = [[NSString alloc] initWithData: self encoding: NSUTF8StringEncoding]; const char* pass = [thePassword bytes]; result = crypto_pwhash_str_verify([cryptString UTF8String], pass, [thePassword length]); [cryptString release]; return result == 0; } #endif /* HAVE_SODIUM */ // encrypt self with the salt an compare the results passwordCrypted = [thePassword asCryptedPassUsingScheme: passwordScheme withSalt: salt keyPath: theKeyPath]; // return always false when there was a problem if (passwordCrypted == nil) return NO; return [self isEqual: passwordCrypted]; } - (NSData *) asLM { NSData *out; unsigned char buf[14]; unsigned char *o; unsigned int len; memset(buf, 0, 14); len = ([self length] >= 14 ? 14 : [self length]); [self getBytes: buf length: len]; o = malloc(16*sizeof(unsigned char)); auth_LMhash(o, buf, len); out = [NSData dataWithBytes: o length: 16]; free(o); return out; } /** * Hash the data with MD4. Uses openssl functions to generate it. * * @return Binary data from MD4 hashing. On error the funciton returns nil. */ - (NSData *) asMD4 { unsigned char md4[MD4_DIGEST_LENGTH]; memset(md4, 0, MD4_DIGEST_LENGTH); #if defined(HAVE_GNUTLS) if (!check_gnutls_init()) return nil; md4_buffer([self bytes], [self length], md4); #elif defined(HAVE_OPENSSL) MD4([self bytes], [self length], md4); #endif return [NSData dataWithBytes: md4 length: MD4_DIGEST_LENGTH]; } /** * Hash the data with MD5. Uses openssl functions to generate it * * @return Binary data from MD5 hashing. On error the funciton returns nil. */ - (NSData *) asMD5 { unsigned char md5[MD5_DIGEST_LENGTH]; memset(md5, 0, MD5_DIGEST_LENGTH); #if defined(HAVE_GNUTLS) if (!check_gnutls_init()) return nil; gnutls_hash_fast (GNUTLS_DIG_MD5, [self bytes], [self length], md5); #elif defined(HAVE_OPENSSL) MD5([self bytes], [self length], md5); #endif return [NSData dataWithBytes: md5 length: MD5_DIGEST_LENGTH]; } /** * Hash the data with CRAM-MD5. Uses openssl functions to generate it. * * Note that the actual CRAM-MD5 algorithm also needs a challenge * but this is not provided, this function actually calculalates * only the context data which can be used for the challange-response * algorithm then. This is just the underlying algorithm to store the passwords. * * The code is adopts the dovecot behaviour of storing the passwords * * @return Binary data from CRAM-MD5 'hashing'. On error the funciton returns nil. */ - (NSData *) asCramMD5 { #if defined(HAVE_GNUTLS) const uint32_t init_digest[4] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476}; uint32_t digest[4]; #elif defined(HAVE_OPENSSL) MD5_CTX ctx; #endif unsigned char inner[64]; unsigned char outer[64]; unsigned char result[32]; unsigned char *r; int i; int len; NSData *key; if ([self length] > 64) { key = [self asMD5]; } else { key = self; } len = [key length]; // fill with both inner and outer with key memcpy(inner, [key bytes], len); // make sure the rest of the bytes is zero memset(inner + len, 0, 64 - len); memcpy(outer, inner, 64); for (i = 0; i < 64; i++) { inner[i] ^= 0x36; outer[i] ^= 0x5c; } // this transformation is needed for the correct cast to binary data #define CDPUT(p, c) { \ *p = (c) & 0xff; p++; \ *p = (c) >> 8 & 0xff; p++; \ *p = (c) >> 16 & 0xff; p++; \ *p = (c) >> 24 & 0xff; p++; \ } #if defined(HAVE_GNUTLS) // generate first set of context bytes from outer data memcpy(digest, init_digest, sizeof(digest)); _nettle_md5_compress(digest, outer); r = result; // convert this to correct binary data according to RFC 1321 CDPUT(r, digest[0]); CDPUT(r, digest[1]); CDPUT(r, digest[2]); CDPUT(r, digest[3]); // second set with inner data is appended to result string memcpy(digest, init_digest, sizeof(digest)); _nettle_md5_compress(digest, inner); // convert this to correct binary data CDPUT(r, digest[0]); CDPUT(r, digest[1]); CDPUT(r, digest[2]); CDPUT(r, digest[3]); #elif defined(HAVE_OPENSSL) // generate first set of context bytes from outer data MD5_Init(&ctx); MD5_Transform(&ctx, outer); r = result; // convert this to correct binary data according to RFC 1321 CDPUT(r, ctx.A); CDPUT(r, ctx.B); CDPUT(r, ctx.C); CDPUT(r, ctx.D); // second set with inner data is appended to result string MD5_Init(&ctx); MD5_Transform(&ctx, inner); // convert this to correct binary data CDPUT(r, ctx.A); CDPUT(r, ctx.B); CDPUT(r, ctx.C); CDPUT(r, ctx.D); #endif return [NSData dataWithBytes: result length: 32]; } /** * Hash the data with SHA1. Uses openssl functions to generate it. * * @return Binary data from SHA1 hashing. On error the funciton returns nil. */ - (NSData *) asSHA1 { unsigned char sha[SHA_DIGEST_LENGTH]; memset(sha, 0, SHA_DIGEST_LENGTH); #if defined(HAVE_GNUTLS) if (!check_gnutls_init()) return nil; gnutls_hash_fast (GNUTLS_DIG_SHA1, [self bytes], [self length], sha); #elif defined(HAVE_OPENSSL) SHA1([self bytes], [self length], sha); #endif return [NSData dataWithBytes: sha length: SHA_DIGEST_LENGTH]; } /** * Hash the data with SHA256. Uses openssl functions to generate it. * * @return Binary data from SHA256 hashing. On error the funciton returns nil. */ - (NSData *) asSHA256 { unsigned char sha[SHA256_DIGEST_LENGTH]; memset(sha, 0, SHA256_DIGEST_LENGTH); #if defined(HAVE_GNUTLS) if (!check_gnutls_init()) return nil; gnutls_hash_fast (GNUTLS_DIG_SHA256, [self bytes], [self length], sha); #elif defined(HAVE_OPENSSL) SHA256([self bytes], [self length], sha); #endif return [NSData dataWithBytes: sha length: SHA256_DIGEST_LENGTH]; } /** * Hash the data with SHA512. Uses openssl functions to generate it. * * @return Binary data from SHA512 hashing. On error the funciton returns nil. */ - (NSData *) asSHA512 { unsigned char sha[SHA512_DIGEST_LENGTH]; memset(sha, 0, SHA512_DIGEST_LENGTH); #if defined(HAVE_GNUTLS) if (!check_gnutls_init()) return nil; gnutls_hash_fast (GNUTLS_DIG_SHA512, [self bytes], [self length], sha); #elif defined(HAVE_OPENSSL) SHA512([self bytes], [self length], sha); #endif return [NSData dataWithBytes: sha length: SHA512_DIGEST_LENGTH]; } - (NSData *) asSymAES128CBCUsingIV: (NSString *) theIV keyPath: (NSString *) theKeyPath { NSData *iv_d, *key_d, *cipherdata; NSMutableString *result; NSString *s; char ciphertext[256], *iv_s, *key_s, *pass; unsigned int len; len = ceil((double)[self length]/16) * 16; if (theIV) iv_d = [theIV dataByDecodingBase64]; else { iv_d = [NSData generateSaltForLength: len]; theIV = [iv_d stringByEncodingBase64]; } iv_s = calloc([iv_d length]+1, sizeof(char)); strncpy(iv_s, [iv_d bytes], [iv_d length]); key_d = [NSData dataWithContentsOfFile: theKeyPath]; key_s = calloc([key_d length]+1, sizeof(char)); strncpy(key_s, [key_d bytes], [key_d length]); pass = calloc(len, sizeof(char)); strncpy(pass, [self bytes], [self length]); AES128_CBC_encrypt_buffer((uint8_t*)ciphertext, (uint8_t*)pass, (uint32_t)len, (const uint8_t*)key_s, (const uint8_t*)iv_s); cipherdata = [NSData dataWithBytes: ciphertext length: 16]; s = [[NSString alloc] initWithData: [cipherdata dataByEncodingBase64WithLineLength: 1024] encoding: NSASCIIStringEncoding]; result = [NSMutableString string]; [result appendFormat: @"$AES-128-CBC$%@$%@", theIV, s]; RELEASE(s); return [result dataUsingEncoding: NSUTF8StringEncoding]; } /** * Hash the data with SSHA. Uses openssl functions to generate it. * * SSHA works following: SSHA(pass, salt) = SHA1(pass + salt) + saltData * * @param theSalt The salt to be used must not be nil, if empty, one will be generated * @return Binary data from SHA1 hashing. On error the funciton returns nil. */ - (NSData *) asSSHAUsingSalt: (NSData *) theSalt { // NSMutableData *sshaData; // generate salt, if not available if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8]; // put the pass and salt together as one data array sshaData = [NSMutableData dataWithData: self]; [sshaData appendData: theSalt]; // generate SHA1 from pass + salt sshaData = [NSMutableData dataWithData: [sshaData asSHA1]]; // append salt again [sshaData appendData: theSalt]; return sshaData; } /** * Hash the data with SSHA256. Uses openssl functions to generate it. * * SSHA256 works following: SSHA256(pass, salt) = SHA256(pass + salt) + saltData * * @param theSalt The salt to be used must not be nil, if empty, one will be generated * @return Binary data from SHA1 hashing. On error the funciton returns nil. */ - (NSData *) asSSHA256UsingSalt: (NSData *) theSalt { NSMutableData *sshaData; // generate salt, if not available if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8]; // put the pass and salt together as one data array sshaData = [NSMutableData dataWithData: self]; [sshaData appendData: theSalt]; // generate SHA1 from pass + salt sshaData = [NSMutableData dataWithData: [sshaData asSHA256]]; // append salt again [sshaData appendData: theSalt]; return sshaData; } /** * Hash the data with SSHA512. Uses openssl functions to generate it. * * SSHA works following: SSHA512(pass, salt) = SHA512(pass + salt) + saltData * * @param theSalt The salt to be used must not be nil, if empty, one will be generated * @return Binary data from SHA512 hashing. On error the funciton returns nil. */ - (NSData *) asSSHA512UsingSalt: (NSData *) theSalt { NSMutableData *sshaData; // generate salt, if not available if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8]; // put the pass and salt together as one data array sshaData = [NSMutableData dataWithData: self]; [sshaData appendData: theSalt]; // generate SHA1 from pass + salt sshaData = [NSMutableData dataWithData: [sshaData asSHA512]]; // append salt again [sshaData appendData: theSalt]; return sshaData; } /** * Hash the data with SMD5. Uses openssl functions to generate it. * * SMD5 works following: SMD5(pass, salt) = MD5(pass + salt) + saltData * * @param theSalt The salt to be used must not be nil, if empty, one will be generated * @return Binary data from SMD5 hashing. On error the funciton returns nil. */ - (NSData *) asSMD5UsingSalt: (NSData *) theSalt { // SMD5 works following: SMD5(pass, salt) = MD5(pass + salt) + salt NSMutableData *smdData; // generate salt, if not available if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8]; // put the pass and salt together as one data array smdData = [NSMutableData dataWithData: self]; [smdData appendData: theSalt]; // generate SHA1 from pass + salt smdData = [NSMutableData dataWithData: [smdData asMD5]]; // append salt again [smdData appendData: theSalt]; return smdData; } // // Internal hashing function using glibc's crypt() one. // Glibc 2.6 supports magic == 5 and 6 // - (NSData *) _asCryptedUsingSalt: (NSData *) theSalt magic: (NSString *) magic { NSString *cryptString, *saltString; NSMutableData *saltData; char *buf; if ([theSalt length] == 0) { // make sure these characters are all printable theSalt = [NSData generateSaltForLength: 8 withPrintable: YES]; } cryptString = [[NSString alloc] initWithData: self encoding: NSUTF8StringEncoding]; saltData = [NSMutableData dataWithData: [[NSString stringWithFormat:@"$%@$", magic] dataUsingEncoding: NSUTF8StringEncoding]]; [saltData appendData: theSalt]; // Terminate with "$" [saltData appendData: [@"$" dataUsingEncoding: NSUTF8StringEncoding]]; saltString = [[NSString alloc] initWithData: saltData encoding: NSUTF8StringEncoding]; buf = crypt([cryptString UTF8String], [saltString UTF8String]); [cryptString release]; [saltString release]; if (!buf) return nil; return [NSData dataWithBytes: buf length: strlen(buf)]; } /** * Hash the data with CRYPT-MD5 as used in /etc/passwd nowadays. Uses crypt() function to generate it. * * @param theSalt The salt to be used must not be nil, if empty, one will be generated. It must be printable characters only. * @return Binary data from CRYPT-MD5 hashing. On error the funciton returns nil. */ - (NSData *) asMD5CryptUsingSalt: (NSData *) theSalt { return [self _asCryptedUsingSalt: theSalt magic: @"1"]; } /** * Hash the data with CRYPT-SHA256 as used in /etc/passwd nowadays. Uses crypt() function to generate it. * * @param theSalt The salt to be used must not be nil, if empty, one will be generated. It must be printable characters only. * @return Binary data from CRYPT-SHA256 hashing. On error the funciton returns nil. */ - (NSData *) asSHA256CryptUsingSalt: (NSData *) theSalt { return [self _asCryptedUsingSalt: theSalt magic: @"5"]; } /** * Hash the data with CRYPT-SHA512 as used in /etc/passwd nowadays. Uses crypt() function to generate it. * * @param theSalt The salt to be used must not be nil, if empty, one will be generated. It must be printable characters only. * @return Binary data from CRYPT-SHA512 hashing. On error the funciton returns nil. */ - (NSData *) asSHA512CryptUsingSalt: (NSData *) theSalt { return [self _asCryptedUsingSalt: theSalt magic: @"6"]; } /** * Hash the data using crypt() function. * * @param theSalt The salt to be used must not be nil, if empty, one will be generated * @return Binary data from CRYPT-MD5 hashing. On error the funciton returns nil. */ - (NSData *) asCryptUsingSalt: (NSData *) theSalt { char *buf; NSString *saltString; NSString *cryptString; // crypt() works with strings, so convert NSData to strings cryptString = [[NSString alloc] initWithData: self encoding: NSUTF8StringEncoding]; if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8 withPrintable: YES]; saltString = [[NSString alloc] initWithData: theSalt encoding: NSUTF8StringEncoding]; // The salt is weak here, but who cares anyway, crypt should not // be used anymore buf = crypt([cryptString UTF8String], [saltString UTF8String]); [saltString release]; [cryptString release]; if (!buf) return nil; return [NSData dataWithBytes: buf length: strlen(buf)]; } /** * Hash the data using blowfish-crypt * @param theSalt The salt to be used must not be nil, if empty, one will be generated */ - (NSData *) asBlowfishCryptUsingSalt: (NSData *) theSalt { NSString *cleartext; char hashed_password[BLF_CRYPT_BUFFER_LEN]; char magic_salt[BLF_CRYPT_PREFIX_LEN]; // contains $2.$nn$ + salt if ([theSalt length] == 0) { // generate a salt with default complexity if none was provided NSData* salt = [NSData generateSaltForLength: BLF_CRYPT_SALT_LEN]; if (_crypt_gensalt_blowfish_rn(BLF_CRYPT_PREFIX, BLF_CRYPT_DEFAULT_COMPLEXITY, [salt bytes], BLF_CRYPT_SALT_LEN, magic_salt, BLF_CRYPT_PREFIX_LEN) == NULL) return nil; } else { const char* salt = [theSalt bytes]; if ([theSalt length] < BLF_CRYPT_PREFIX_LEN || salt[0] != '$' || salt[1] != '2' || salt[2] < 'a' || salt[2] > 'z' || salt[3] != '$') { return nil; } memcpy(magic_salt, salt, BLF_CRYPT_PREFIX_LEN); } cleartext = [[NSString alloc] initWithData: self encoding: NSUTF8StringEncoding]; const char* password = [cleartext UTF8String]; char* bf_res = _crypt_blowfish_rn(password, magic_salt, hashed_password, BLF_CRYPT_BUFFER_LEN); [cleartext autorelease]; if (bf_res == NULL) return nil; return [NSData dataWithBytes: hashed_password length: strlen(hashed_password)]; } - (NSData *) asPBKDF2SHA1UsingSalt: (NSData *) theSalt { NSString *saltString; unsigned char hashed_password[PBKDF2_KEY_SIZE_SHA1] = {0}; int rounds = 0; if ([theSalt length] == 0) { // generate a salt with default complexity if none was provided NSData* saltData = [NSData generateSaltForLength: PBKDF2_SALT_LEN withPrintable: YES]; saltString = [[NSString alloc] initWithData: saltData encoding: NSUTF8StringEncoding]; [saltString autorelease]; } else { NSString *saltAndRounds; NSArray *saltAndRoundsComponents; saltAndRounds = [[NSString alloc] initWithData: theSalt encoding: NSUTF8StringEncoding]; // salt is expected to be of the form salt$rounds saltAndRoundsComponents = [saltAndRounds componentsSeparatedByString: @"$"]; AUTORELEASE(saltAndRounds); if ([saltAndRoundsComponents count] != 2) { return nil; } saltString = [saltAndRoundsComponents objectAtIndex: 0]; rounds = [[saltAndRoundsComponents objectAtIndex: 1] intValue]; } if (rounds == 0) rounds = PBKDF2_DEFAULT_ROUNDS; const char* password = [self bytes]; const unsigned char* salt = (const unsigned char*)[saltString UTF8String]; #if defined(HAVE_GNUTLS) if (!check_gnutls_init()) return nil; #endif if (pkcs5_pbkdf2(password, [self length], salt, PBKDF2_SALT_LEN, hashed_password, PBKDF2_KEY_SIZE_SHA1, rounds) != 0) { return nil; } NSData *passwordData = [NSData dataWithBytesNoCopy: hashed_password length: PBKDF2_KEY_SIZE_SHA1 freeWhenDone: NO]; NSString *hexHash = [NSData encodeDataAsHexString: passwordData]; NSString* result = [NSString stringWithFormat: @"$1$%@$%u$%@", saltString, rounds, hexHash]; return [result dataUsingEncoding:NSUTF8StringEncoding]; } #ifdef HAVE_SODIUM - (NSData *) asArgon2iUsingSalt: (NSData *) theSalt { char hashed_password[crypto_pwhash_argon2i_STRBYTES]; int rounds = crypto_pwhash_argon2i_OPSLIMIT_INTERACTIVE; size_t memlimit = crypto_pwhash_argon2i_MEMLIMIT_INTERACTIVE; if (sodium_init() < 0) return nil; const char* password = [self bytes]; if (crypto_pwhash_argon2i_str(hashed_password, password, [self length], rounds, memlimit) != 0) return nil; return [NSData dataWithBytes: hashed_password length: strlen(hashed_password)]; } # ifdef crypto_pwhash_ALG_ARGON2ID13 - (NSData *) asArgon2idUsingSalt: (NSData *) theSalt; { char hashed_password[crypto_pwhash_argon2id_STRBYTES]; int rounds = crypto_pwhash_argon2id_OPSLIMIT_INTERACTIVE; size_t memlimit = crypto_pwhash_argon2id_MEMLIMIT_INTERACTIVE; if (sodium_init() < 0) return nil; const char* password = [self bytes]; if (crypto_pwhash_argon2id_str(hashed_password, password, [self length], rounds, memlimit) != 0) return nil; return [NSData dataWithBytes: hashed_password length: strlen(hashed_password)]; } #endif /* crypto_pwhash_ALG_ARGON2ID13 */ #endif /* HAVE_SODIUM */ /** * Get the salt from a password encrypted with a specied scheme * * @param theScheme Needed to get the salt correctly out of the pass * @return The salt, if one was available in the password/scheme, else empty data */ - (NSData *) extractSalt: (NSString *) theScheme { NSRange r; int len; len = [self length]; if (len == 0) return [NSData data]; // for the ssha schemes the salt is appended at the endif // so the range with the salt are bytes after each digest length if ([theScheme caseInsensitiveCompare: @"crypt"] == NSOrderedSame || [theScheme caseInsensitiveCompare: @"blf-crypt"] == NSOrderedSame) { // for (blf-)crypt schemes simply use the whole string // the crypt() function is able to extract it by itself r = NSMakeRange(0, len); } else if ([theScheme caseInsensitiveCompare: @"md5-crypt"] == NSOrderedSame || [theScheme caseInsensitiveCompare: @"sha256-crypt"] == NSOrderedSame || [theScheme caseInsensitiveCompare: @"sha512-crypt"] == NSOrderedSame || [theScheme caseInsensitiveCompare: @"pbkdf2"] == NSOrderedSame) { // md5-crypt is generated the following "$1$$" // sha256-crypt is generated the following "$5$$" // sha512-crypt is generated the following "$6$$" // pbkdf2 is generated as "$1$$$" NSString *cryptString; NSArray *cryptParts; cryptString = [[NSString alloc] initWithData: self encoding: NSUTF8StringEncoding]; AUTORELEASE(cryptString); cryptParts = [cryptString componentsSeparatedByString: @"$"]; // correct number of elements (first one is an empty string) if ([cryptParts count] < 4) { return [NSData data]; } // second is the identifier of md5-crypt/sha256-crypt or sha512-crypt else if ([[cryptParts objectAtIndex: 1] caseInsensitiveCompare: @"1"] == NSOrderedSame || [[cryptParts objectAtIndex: 1] caseInsensitiveCompare: @"5"] == NSOrderedSame || [[cryptParts objectAtIndex: 1] caseInsensitiveCompare: @"6"] == NSOrderedSame) { // third is the salt; convert it to NSData if ([cryptParts count] == 4) return [[cryptParts objectAtIndex: 2] dataUsingEncoding: NSUTF8StringEncoding]; else { NSString *saltWithRounds; saltWithRounds = [NSString stringWithFormat: @"%@$%@", [cryptParts objectAtIndex: 2], [cryptParts objectAtIndex: 3]]; return [saltWithRounds dataUsingEncoding: NSUTF8StringEncoding]; } } // nothing good return [NSData data]; } else if ([theScheme caseInsensitiveCompare: @"ssha"] == NSOrderedSame) { r = NSMakeRange(SHA_DIGEST_LENGTH, len - SHA_DIGEST_LENGTH); } else if ([theScheme caseInsensitiveCompare: @"ssha256"] == NSOrderedSame) { r = NSMakeRange(SHA256_DIGEST_LENGTH, len - SHA256_DIGEST_LENGTH); } else if ([theScheme caseInsensitiveCompare: @"ssha512"] == NSOrderedSame) { r = NSMakeRange(SHA512_DIGEST_LENGTH, len - SHA512_DIGEST_LENGTH); } else if ([theScheme caseInsensitiveCompare: @"smd5"] == NSOrderedSame) { r = NSMakeRange(MD5_DIGEST_LENGTH, len - MD5_DIGEST_LENGTH); } else if ([[theScheme lowercaseString] hasPrefix: @"sym"]) { // For sym we return everything r = NSMakeRange(0, len); } else { // return empty string on unknown scheme return [NSData data]; } return [self subdataWithRange: r]; } @end static unsigned charTo4Bits(char c) { unsigned bits = 0; if (c > '/' && c < ':') { bits = c - '0'; } else if (c > '@' && c < 'G') { bits = (c- 'A') + 10; } else if (c > '`' && c < 'g') { bits = (c- 'a') + 10; } else { bits = 255; } return bits; } #if defined(HAVE_GNUTLS) static BOOL didGlobalInit = NO; static BOOL check_gnutls_init(void) { if (!didGlobalInit) { /* Global system initialization*/ if (gnutls_global_init()) { return NO; } didGlobalInit = YES; } return YES; } /* nettle, low-level cryptographics library * * Copyright (C) 2001, 2005 Niels Möller * * The nettle library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or (at your * option) any later version. * * The nettle library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the nettle library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, * MA 02111-1307, USA. */ /* Based on public domain code hacked by Colin Plumb, Andrew Kuchling, and * Niels Möller. */ #define LE_READ_UINT32(p) \ ( (((uint32_t) (p)[3]) << 24) \ | (((uint32_t) (p)[2]) << 16) \ | (((uint32_t) (p)[1]) << 8) \ | ((uint32_t) (p)[0])) /* MD5 functions */ #define F1(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) #define F2(x, y, z) F1((z), (x), (y)) #define F3(x, y, z) ((x) ^ (y) ^ (z)) #define F4(x, y, z) ((y) ^ ((x) | ~(z))) #define ROUND(f, w, x, y, z, data, s) \ ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input) { uint32_t data[MD5_DIGEST_LENGTH]; uint32_t a, b, c, d; unsigned i; for (i = 0; i < MD5_DIGEST_LENGTH; i++, input += 4) data[i] = LE_READ_UINT32(input); a = digest[0]; b = digest[1]; c = digest[2]; d = digest[3]; ROUND(F1, a, b, c, d, data[ 0] + 0xd76aa478, 7); ROUND(F1, d, a, b, c, data[ 1] + 0xe8c7b756, 12); ROUND(F1, c, d, a, b, data[ 2] + 0x242070db, 17); ROUND(F1, b, c, d, a, data[ 3] + 0xc1bdceee, 22); ROUND(F1, a, b, c, d, data[ 4] + 0xf57c0faf, 7); ROUND(F1, d, a, b, c, data[ 5] + 0x4787c62a, 12); ROUND(F1, c, d, a, b, data[ 6] + 0xa8304613, 17); ROUND(F1, b, c, d, a, data[ 7] + 0xfd469501, 22); ROUND(F1, a, b, c, d, data[ 8] + 0x698098d8, 7); ROUND(F1, d, a, b, c, data[ 9] + 0x8b44f7af, 12); ROUND(F1, c, d, a, b, data[10] + 0xffff5bb1, 17); ROUND(F1, b, c, d, a, data[11] + 0x895cd7be, 22); ROUND(F1, a, b, c, d, data[12] + 0x6b901122, 7); ROUND(F1, d, a, b, c, data[13] + 0xfd987193, 12); ROUND(F1, c, d, a, b, data[14] + 0xa679438e, 17); ROUND(F1, b, c, d, a, data[15] + 0x49b40821, 22); ROUND(F2, a, b, c, d, data[ 1] + 0xf61e2562, 5); ROUND(F2, d, a, b, c, data[ 6] + 0xc040b340, 9); ROUND(F2, c, d, a, b, data[11] + 0x265e5a51, 14); ROUND(F2, b, c, d, a, data[ 0] + 0xe9b6c7aa, 20); ROUND(F2, a, b, c, d, data[ 5] + 0xd62f105d, 5); ROUND(F2, d, a, b, c, data[10] + 0x02441453, 9); ROUND(F2, c, d, a, b, data[15] + 0xd8a1e681, 14); ROUND(F2, b, c, d, a, data[ 4] + 0xe7d3fbc8, 20); ROUND(F2, a, b, c, d, data[ 9] + 0x21e1cde6, 5); ROUND(F2, d, a, b, c, data[14] + 0xc33707d6, 9); ROUND(F2, c, d, a, b, data[ 3] + 0xf4d50d87, 14); ROUND(F2, b, c, d, a, data[ 8] + 0x455a14ed, 20); ROUND(F2, a, b, c, d, data[13] + 0xa9e3e905, 5); ROUND(F2, d, a, b, c, data[ 2] + 0xfcefa3f8, 9); ROUND(F2, c, d, a, b, data[ 7] + 0x676f02d9, 14); ROUND(F2, b, c, d, a, data[12] + 0x8d2a4c8a, 20); ROUND(F3, a, b, c, d, data[ 5] + 0xfffa3942, 4); ROUND(F3, d, a, b, c, data[ 8] + 0x8771f681, 11); ROUND(F3, c, d, a, b, data[11] + 0x6d9d6122, 16); ROUND(F3, b, c, d, a, data[14] + 0xfde5380c, 23); ROUND(F3, a, b, c, d, data[ 1] + 0xa4beea44, 4); ROUND(F3, d, a, b, c, data[ 4] + 0x4bdecfa9, 11); ROUND(F3, c, d, a, b, data[ 7] + 0xf6bb4b60, 16); ROUND(F3, b, c, d, a, data[10] + 0xbebfbc70, 23); ROUND(F3, a, b, c, d, data[13] + 0x289b7ec6, 4); ROUND(F3, d, a, b, c, data[ 0] + 0xeaa127fa, 11); ROUND(F3, c, d, a, b, data[ 3] + 0xd4ef3085, 16); ROUND(F3, b, c, d, a, data[ 6] + 0x04881d05, 23); ROUND(F3, a, b, c, d, data[ 9] + 0xd9d4d039, 4); ROUND(F3, d, a, b, c, data[12] + 0xe6db99e5, 11); ROUND(F3, c, d, a, b, data[15] + 0x1fa27cf8, 16); ROUND(F3, b, c, d, a, data[ 2] + 0xc4ac5665, 23); ROUND(F4, a, b, c, d, data[ 0] + 0xf4292244, 6); ROUND(F4, d, a, b, c, data[ 7] + 0x432aff97, 10); ROUND(F4, c, d, a, b, data[14] + 0xab9423a7, 15); ROUND(F4, b, c, d, a, data[ 5] + 0xfc93a039, 21); ROUND(F4, a, b, c, d, data[12] + 0x655b59c3, 6); ROUND(F4, d, a, b, c, data[ 3] + 0x8f0ccc92, 10); ROUND(F4, c, d, a, b, data[10] + 0xffeff47d, 15); ROUND(F4, b, c, d, a, data[ 1] + 0x85845dd1, 21); ROUND(F4, a, b, c, d, data[ 8] + 0x6fa87e4f, 6); ROUND(F4, d, a, b, c, data[15] + 0xfe2ce6e0, 10); ROUND(F4, c, d, a, b, data[ 6] + 0xa3014314, 15); ROUND(F4, b, c, d, a, data[13] + 0x4e0811a1, 21); ROUND(F4, a, b, c, d, data[ 4] + 0xf7537e82, 6); ROUND(F4, d, a, b, c, data[11] + 0xbd3af235, 10); ROUND(F4, c, d, a, b, data[ 2] + 0x2ad7d2bb, 15); ROUND(F4, b, c, d, a, data[ 9] + 0xeb86d391, 21); digest[0] += a; digest[1] += b; digest[2] += c; digest[3] += d; } #endif