문제

For example, the command:

openssl enc -aes-256-cbc -a -in test.txt -k pinkrhino -nosalt -p -out openssl_output.txt

outputs something like:

key = 33D890D33F91D52FC9B405A0DDA65336C3C4B557A3D79FE69AB674BE82C5C3D2
iv  = 677C95C475C0E057B739750748608A49

How is that key generated? (C code as an answer would be too awesome to ask for :) ) Also, how is the iv generated?

Looks like some kind of hex to me.

도움이 되었습니까?

해결책

OpenSSL uses the function EVP_BytesToKey. You can find the call to it in apps/enc.c. The enc utility used to use the MD5 digest by default in the Key Derivation Algorithm (KDF) if you didn't specify a different digest with the -md argument. Now it uses SHA-256 by default. Here's a working example using MD5:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/evp.h>

int main(int argc, char *argv[])
{
    const EVP_CIPHER *cipher;
    const EVP_MD *dgst = NULL;
    unsigned char key[EVP_MAX_KEY_LENGTH], iv[EVP_MAX_IV_LENGTH];
    const char *password = "password";
    const unsigned char *salt = NULL;
    int i;

    OpenSSL_add_all_algorithms();

    cipher = EVP_get_cipherbyname("aes-256-cbc");
    if(!cipher) { fprintf(stderr, "no such cipher\n"); return 1; }

    dgst=EVP_get_digestbyname("md5");
    if(!dgst) { fprintf(stderr, "no such digest\n"); return 1; }

    if(!EVP_BytesToKey(cipher, dgst, salt,
        (unsigned char *) password,
        strlen(password), 1, key, iv))
    {
        fprintf(stderr, "EVP_BytesToKey failed\n");
        return 1;
    }

    printf("Key: "); for(i=0; i<cipher->key_len; ++i) { printf("%02x", key[i]); } printf("\n");
    printf("IV: "); for(i=0; i<cipher->iv_len; ++i) { printf("%02x", iv[i]); } printf("\n");

    return 0;
}

Example usage:

gcc b2k.c -o b2k -lcrypto -g
./b2k
Key: 5f4dcc3b5aa765d61d8327deb882cf992b95990a9151374abd8ff8c5a7a0fe08
IV: b7b4372cdfbcb3d16a2631b59b509e94

Which generates the same key as this OpenSSL command line:

openssl enc -aes-256-cbc -k password -nosalt -p < /dev/null
key=5F4DCC3B5AA765D61D8327DEB882CF992B95990A9151374ABD8FF8C5A7A0FE08
iv =B7B4372CDFBCB3D16A2631B59B509E94

OpenSSL 1.1.0c changed the digest algorithm used in some internal components. Formerly, MD5 was used, and 1.1.0 switched to SHA256. Be careful the change is not affecting you in both EVP_BytesToKey and commands like openssl enc.

다른 팁

If anyone is looking for implementing the same in SWIFT I converted the EVP_BytesToKey in swift

 /*
 - parameter keyLen: keyLen
 - parameter ivLen:  ivLen
 - parameter digest: digest e.g "md5" or "sha1"
 - parameter salt:   salt
 - parameter data:   data
 - parameter count:  count

 - returns: key and IV respectively
 */
open static func evpBytesToKey(_ keyLen:Int, ivLen:Int, digest:String, salt:[UInt8], data:Data, count:Int)-> [[UInt8]] {
    let saltData = Data(bytes: UnsafePointer<UInt8>(salt), count: Int(salt.count))
    var both = [[UInt8]](repeating: [UInt8](), count: 2)
    var key = [UInt8](repeating: 0,count: keyLen)
    var key_ix = 0
    var iv = [UInt8](repeating: 0,count: ivLen)
    var iv_ix = 0

    var nkey = keyLen;
    var niv = ivLen;

    var i = 0
    var addmd = 0
    var md:Data = Data()
    var md_buf:[UInt8]

    while true {

        addmd = addmd + 1
        md.append(data)
        md.append(saltData)

        if(digest=="md5"){
            md = NSData(data:md.md5()) as Data
        }else if (digest == "sha1"){
            md = NSData(data:md.sha1()) as Data
        }

        for _ in 1...(count-1){

            if(digest=="md5"){
                md = NSData(data:md.md5()) as Data
            }else if (digest == "sha1"){
                md = NSData(data:md.sha1()) as Data
            }
        }
        md_buf = Array (UnsafeBufferPointer(start: md.bytes, count: md.count))
        //            md_buf = Array(UnsafeBufferPointer(start: md.bytes.bindMemory(to: UInt8.self, capacity: md.count), count: md.length))
        i = 0
        if (nkey > 0) {
            while(true) {
                if (nkey == 0){
                    break
                }
                if (i == md.count){
                    break
                }
                key[key_ix] = md_buf[i];
                key_ix = key_ix + 1
                nkey = nkey - 1
                i = i + 1
            }
        }
        if (niv > 0 && i != md_buf.count) {
            while(true) {
                if (niv == 0){
                    break
                }
                if (i == md_buf.count){
                    break
                }
                iv[iv_ix] = md_buf[i]
                iv_ix = iv_ix + 1
                niv = niv - 1
                i = i + 1
            }
        }
        if (nkey == 0 && niv == 0) {
            break
        }

    }
    both[0] = key
    both[1] = iv

    return both

}

I use CryptoSwift for the hash. This is a much cleaner way as apples does not recommend OpenSSL in iOS

UPDATE : Swift 3

Here is a version for mbedTLS / Polar SSL - tested and working.


typedef int bool;
#define false 0
#define true (!false)
//------------------------------------------------------------------------------
static bool EVP_BytesToKey( const unsigned int nDesiredKeyLen, const unsigned char* salt,
                            const unsigned char* password, const unsigned int nPwdLen,
                            unsigned char* pOutKey, unsigned char* pOutIV )
{
    // This is a re-implemntation of openssl's password to key & IV routine for mbedtls.
    //  (See openssl apps/enc.c and /crypto/evp/evp_key.c) It is not any kind of
    //  standard (e.g. PBKDF2), and it only uses an interation count of 1, so it's
    //  pretty crappy. MD5 is used as the digest in Openssl 1.0.2, 1.1 and late
    //  use SHA256. Since this is for embedded system, I figure you know what you've
    //  got, so I made it compile-time configurable.
    //
    //  The signature has been re-jiggered to make it less general. 
    //
    //  See: https://wiki.openssl.org/index.php/Manual:EVP_BytesToKey(3)
    //  And: https://www.cryptopp.com/wiki/OPENSSL_EVP_BytesToKey

#define IV_BYTE_COUNT     16

#if BTK_USE_MD5
#  define DIGEST_BYTE_COUNT 16 // MD5
#else
#  define DIGEST_BYTE_COUNT 32 // SHA
#endif

    bool bRet;
    unsigned char md_buf[ DIGEST_BYTE_COUNT ];
    mbedtls_md_context_t md_ctx;
    bool bAddLastMD = false;
    unsigned int nKeyToGo = nDesiredKeyLen;  // 32, typical
    unsigned int nIVToGo  = IV_BYTE_COUNT;

    mbedtls_md_init( &md_ctx );

#if BTK_USE_MD5
    int rc = mbedtls_md_setup( &md_ctx, mbedtls_md_info_from_type( MBEDTLS_MD_MD5  ), 0 ); 
#else
    int rc = mbedtls_md_setup( &md_ctx, mbedtls_md_info_from_type( MBEDTLS_MD_SHA256 ), 0 ); 
#endif

    if (rc != 0 )
    {
        fprintf( stderr, "mbedutils_md_setup() failed -0x%04x\n", -rc );
        bRet = false;
        goto exit;
    }

    while( 1 )
    {
        mbedtls_md_starts( &md_ctx );  // start digest

        if ( bAddLastMD == false )  // first time
        {
            bAddLastMD = true;      // do it next time
        }
        else
        {
            mbedtls_md_update( &md_ctx, &md_buf[0], DIGEST_BYTE_COUNT );
        }

        mbedtls_md_update( &md_ctx, &password[0], nPwdLen );
        mbedtls_md_update( &md_ctx, &salt[0], 8 );
        mbedtls_md_finish( &md_ctx, &md_buf[0] );

        //
        // Iteration loop here in original removed as unused by "openssl enc"
        //

        // Following code treats the output key and iv as one long, concatentated buffer
        //   and smears as much digest across it as is available. If not enough, it takes the
        //   big, enclosing loop, makes more digest, and continues where it left off on
        //   the last iteration.
        unsigned int ii = 0;  // index into mb_buf

        if ( nKeyToGo != 0 )    // still have key to fill in?
        {
            while( 1 )
            {
                if ( nKeyToGo == 0 )               // key part is full/done
                    break;
                if ( ii == DIGEST_BYTE_COUNT )     // ran out of digest, so loop
                    break;

                *pOutKey++ = md_buf[ ii ];         // stick byte in output key
                nKeyToGo--;
                ii++;
            }
        }

        if ( nIVToGo != 0                    // still have fill up IV
             &&                              // and
             ii != DIGEST_BYTE_COUNT         // have some digest available
           )
        {
            while( 1 )
            {
                if ( nIVToGo == 0 )              // iv is full/done
                    break;
                if ( ii == DIGEST_BYTE_COUNT )   // ran out of digest, so loop
                    break;
                *pOutIV++ = md_buf[ ii ];        // stick byte in output IV
                nIVToGo--;
                ii++;
            }
        }

        if ( nKeyToGo == 0  && nIVToGo == 0 )    // output full, break main loop and exit
            break;
    } // outermost while loop

    bRet = true;

  exit:
    mbedtls_md_free( &md_ctx );
    return bRet;
}

If anyone passing through here is looking for a working, performant reference implementation in Haskell, here it is:

import Crypto.Hash
import qualified Data.ByteString    as B
import Data.ByteArray               (convert)
import Data.Monoid                  ((<>))

evpBytesToKey :: HashAlgorithm alg =>
    Int -> Int -> alg -> Maybe B.ByteString -> B.ByteString -> (B.ByteString, B.ByteString)
evpBytesToKey keyLen ivLen alg mSalt password =
    let bytes       = B.concat . take required . iterate go $ hash' passAndSalt
        (key, rest) = B.splitAt keyLen bytes
    in (key, B.take ivLen rest)
  where
    hash'       = convert . hashWith alg
    required    = 1 + ((keyLen + ivLen - 1) `div` hashDigestSize alg)
    passAndSalt = maybe password (password <>) mSalt
    go          = hash' . (<> passAndSalt)

It uses hash algorithms provided by the cryptonite package. The arguments are desired key and IV size in bytes, the hash algorithm to use (like e.g. (undefined :: MD5)), optional salt and the password. The result is a tuple of key and IV.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top