Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -85,10 +85,11 @@ OFZIPArchiveEntry.m \ base64.m \ crc32.m \ of_asprintf.m \ of_strptime.m \ + pbkdf2.m \ unicode.m \ ${USE_SRCS_FILES} \ ${USE_SRCS_PLUGINS} \ ${USE_SRCS_SOCKETS} \ ${USE_SRCS_THREADS} Index: src/OFHMAC.h ================================================================== --- src/OFHMAC.h +++ src/OFHMAC.h @@ -103,8 +103,16 @@ * @warning This invalidates any pointer previously returned by @ref digest. If * you are still interested in the previous digest, you need to memcpy * it yourself before calling @ref reset! */ - (void)reset; + +/*! + * @brief This is like @ref reset, but also zeroes the hashed key and all state. + * + * @warning After calling this, you *must* set a new key before reusing the + * HMAC! + */ +- (void)zero; @end OF_ASSUME_NONNULL_END Index: src/OFHMAC.m ================================================================== --- src/OFHMAC.m +++ src/OFHMAC.m @@ -141,8 +141,19 @@ _outerHash = _innerHash = nil; _outerHash = [_outerHashCopy copy]; _innerHash = [_innerHashCopy copy]; + _calculated = false; +} + +- (void)zero +{ + [_outerHash release]; + [_innerHash release]; + [_outerHashCopy release]; + [_innerHashCopy release]; + _outerHash = _innerHash = _outerHashCopy = _innerHashCopy = nil; + _calculated = false; } @end Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -202,5 +202,6 @@ #import "of_asprintf.h" #import "of_strptime.h" #ifdef OF_HAVE_SOCKETS # import "resolver.h" #endif +#import "pbkdf2.h" ADDED src/pbkdf2.h Index: src/pbkdf2.h ================================================================== --- src/pbkdf2.h +++ src/pbkdf2.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS +#endif +#ifndef __STDC_CONSTANT_MACROS +# define __STDC_CONSTANT_MACROS +#endif + +#import "macros.h" + +OF_ASSUME_NONNULL_BEGIN + +/*! @file */ + +@class OFHMAC; + +#ifdef __cplusplus +extern "C" { +#endif +/*! + * @brief Derive a key from a password and a salt. + * + * @note This will call @ref OFHMAC::reset on the @ref OFHMAC first, making it + * possible to reuse the @ref OFHMAC, but also meaning all previous + * results from the @ref OFHMAC get invalidated if they have not been + * copied. + * + * @param HMAC The HMAC to use to derive a key + * @param iterations The number of iterations to perform + * @param salt The salt to derive a key with + * @param saltLength The length of the salt + * @param password The password to derive a key from + * @param passwordLength The length of the password + * @param key The buffer to write the key to + * @param keyLength The desired length for the derived key (key needs to have + * enough storage) + */ +extern void of_pbkdf2(OFHMAC *HMAC, size_t iterations, + const unsigned char *salt, size_t saltLength, + const char *password, size_t passwordLength, + unsigned char *key, size_t keyLength); +#ifdef __cplusplus +} +#endif + +OF_ASSUME_NONNULL_END ADDED src/pbkdf2.m Index: src/pbkdf2.m ================================================================== --- src/pbkdf2.m +++ src/pbkdf2.m @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#import "OFHMAC.h" + +#import "OFInvalidArgumentException.h" +#import "OFOutOfMemoryException.h" +#import "OFOutOfRangeException.h" + +#import "pbkdf2.h" + +void of_pbkdf2(OFHMAC *HMAC, size_t iterations, + const unsigned char *salt, size_t saltLength, + const char *password, size_t passwordLength, + unsigned char *key, size_t keyLength) +{ + size_t blocks, digestSize = [HMAC digestSize]; + unsigned char *extendedSalt; + unsigned char buffer[digestSize]; + unsigned char digest[digestSize]; + + if (HMAC == nil || iterations == 0 || salt == NULL || saltLength == 0 || + password == NULL || key == NULL || keyLength == 0) + @throw [OFInvalidArgumentException exception]; + + blocks = keyLength / digestSize; + if (keyLength % digestSize != 0) + blocks++; + + if (saltLength > SIZE_MAX - 4 || blocks > UINT32_MAX) + @throw [OFOutOfRangeException exception]; + + if ((extendedSalt = malloc(saltLength + 4)) == NULL) + @throw [OFOutOfMemoryException + exceptionWithRequestedSize: saltLength + 4]; + + @try { + uint32_t i = OF_BSWAP32_IF_LE(1); + + [HMAC setKey: password + length: passwordLength]; + + memcpy(extendedSalt, salt, saltLength); + + while (keyLength > 0) { + size_t length; + + memcpy(extendedSalt + saltLength, &i, 4); + + [HMAC reset]; + [HMAC updateWithBuffer: extendedSalt + length: saltLength + 4]; + memcpy(buffer, [HMAC digest], digestSize); + memcpy(digest, [HMAC digest], digestSize); + + for (size_t j = 1; j < iterations; j++) { + [HMAC reset]; + [HMAC updateWithBuffer: digest + length: digestSize]; + memcpy(digest, [HMAC digest], digestSize); + + for (size_t k = 0; k < digestSize; k++) + buffer[k] ^= digest[k]; + } + + length = digestSize; + if (length > keyLength) + length = keyLength; + + memcpy(key, buffer, length); + key += length; + keyLength -= length; + + i = OF_BSWAP32_IF_LE(OF_BSWAP32_IF_LE(i) + 1); + } + } @finally { + memset(extendedSalt, 0, saltLength + 4); + memset(buffer, 0, digestSize); + memset(digest, 0, digestSize); + + [HMAC zero]; + + free(extendedSalt); + } +} Index: tests/Makefile ================================================================== --- tests/Makefile +++ tests/Makefile @@ -24,10 +24,11 @@ OFStringTests.m \ OFURLTests.m \ OFXMLElementBuilderTests.m \ OFXMLNodeTests.m \ OFXMLParserTests.m \ + PBKDF2Tests.m \ RuntimeTests.m \ TestsAppDelegate.m \ ${USE_SRCS_FILES} \ ${USE_SRCS_PLUGINS} \ ${USE_SRCS_SOCKETS} \ ADDED tests/PBKDF2Tests.m Index: tests/PBKDF2Tests.m ================================================================== --- tests/PBKDF2Tests.m +++ tests/PBKDF2Tests.m @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#import "OFHMAC.h" +#import "OFSHA1Hash.h" +#import "OFString.h" +#import "OFAutoreleasePool.h" + +#import "pbkdf2.h" + +#import "TestsAppDelegate.h" + +static OFString *module = @"PBKDF2"; + +@implementation TestsAppDelegate (PBKDF2Tests) +- (void)PBKDF2Tests +{ + OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; + OFHMAC *HMAC = [OFHMAC HMACWithHashClass: [OFSHA1Hash class]]; + unsigned char key[25]; + + /* Test vectors from RFC 6070 */ + + TEST(@"PBKDF2-SHA1, 1 iteration", + R(of_pbkdf2(HMAC, 1, (unsigned char*)"salt", 4, "password", 8, key, + 20)) && + memcmp(key, "\x0C\x60\xC8\x0F\x96\x1F\x0E\x71\xF3\xA9\xB5\x24\xAF" + "\x60\x12\x06\x2F\xE0\x37\xA6", 20) == 0) + + TEST(@"PBKDF2-SHA1, 2 iterations", + R(of_pbkdf2(HMAC, 2, (unsigned char*)"salt", 4, "password", 8, key, + 20)) && + memcmp(key, "\xEA\x6C\x01\x4D\xC7\x2D\x6F\x8C\xCD\x1E\xD9\x2A\xCE" + "\x1D\x41\xF0\xD8\xDE\x89\x57", 20) == 0) + + TEST(@"PBKDF2-SHA1, 4096 iterations", + R(of_pbkdf2(HMAC, 4096, (unsigned char*)"salt", 4, "password", 8, + key, 20)) && + memcmp(key, "\x4B\x00\x79\x01\xB7\x65\x48\x9A\xBE\xAD\x49\xD9\x26" + "\xF7\x21\xD0\x65\xA4\x29\xC1", 20) == 0) + + /* This test takes too long, even on a fast machine. */ +#if 0 + TEST(@"PBKDF2-SHA1, 16777216 iterations", + R(of_pbkdf2(HMAC, 16777216, (unsigned char*)"salt", 4, "password", + 8, key, 20)) && + memcmp(key, "\xEE\xFE\x3D\x61\xCD\x4D\xA4\xE4\xE9\x94\x5B\x3D\x6B" + "\xA2\x15\x8C\x26\x34\xE9\x84", 20) == 0) +#endif + + TEST(@"PBKDF2-SHA1, 4096 iterations, key > 1 block", + R(of_pbkdf2(HMAC, 4096, + (unsigned char*)"saltSALTsaltSALTsaltSALTsaltSALTsalt", 36, + "passwordPASSWORDpassword", 24, key, 25)) && + memcmp(key, "\x3D\x2E\xEC\x4F\xE4\x1C\x84\x9B\x80\xC8\xD8\x36\x62" + "\xC0\xE4\x4A\x8B\x29\x1A\x96\x4C\xF2\xF0\x70\x38", 25) == 0) + + TEST(@"PBKDF2-SHA1, 4096 iterations, key < 1 block", + R(of_pbkdf2(HMAC, 4096, (unsigned char*)"sa\0lt", 5, "pass\0word", + 9, key, 16)) && + memcmp(key, "\x56\xFA\x6A\xA7\x55\x48\x09\x9D\xCC\x37\xD7\xF0\x34" + "\x25\xE0\xC3", 16) == 0) + + [pool drain]; +} +@end Index: tests/TestsAppDelegate.h ================================================================== --- tests/TestsAppDelegate.h +++ tests/TestsAppDelegate.h @@ -206,5 +206,9 @@ @interface TestsAppDelegate (OFXMLParserTests) - (void)XMLParserTests; @end + +@interface TestsAppDelegate (PBKDF2Tests) +- (void)PBKDF2Tests; +@end Index: tests/TestsAppDelegate.m ================================================================== --- tests/TestsAppDelegate.m +++ tests/TestsAppDelegate.m @@ -383,10 +383,11 @@ [self SHA224HashTests]; [self SHA256HashTests]; [self SHA384HashTests]; [self SHA512HashTests]; [self HMACTests]; + [self PBKDF2Tests]; [self INIFileTests]; #endif #ifdef OF_HAVE_SOCKETS [self TCPSocketTests]; [self UDPSocketTests];