Existing implementations for NIST SP 800-56A Concatenation/Single-Step Key Derivation Function? [closed]
Asked Answered
T

3

15

Does anyone know of any existing implementations for NIST SP 800-56A Concatenation Key Derivation Function / CONCAT KDF (preferably in Java)?

The key derivation function is documented in section 5.8.1 of NIST's publication: Recommendation for Pair-Wise Key Establishment Schemes Using Discrete Logarithm Cryptography

Link here: http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf

Microsoft's CNG has an implementation here but if you compare the function implemented by Microsoft, as compared to the parameters documented in NIST SP 800-56A, they do not tally, and the implementation by Microsoft is unusable. I have attempted to implement a sample program in C++ as well but I couldn't match the parameters.

Would anyone be able to attempt to implement it or know of any existing implementations?

I am looking for an implementation that is able to justify why it is accurate to the NIST specifications. I have seen a couple implementations out there and I feel that they are not accurate to the NIST specifications (missing parameters, invalid logic flow etc).

If you can implement it yourself, I am always glad to share my own source code for debate. Thanks! This would be a good contribution to the open source community!

EDIT:

Thanks to @Rasmus Faber, I can finally bring this question to a close, and hope to answer the same question that everyone else has as me.

Here's the code that I have edited based on @Rasmus Faber and my original codes:

ConcatKeyDerivationFunction.java

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 
 * Implementation of Concatenation Key Derivation Function<br/>
 * http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf
 *
 */

public class ConcatKeyDerivationFunction {

    private static final long MAX_HASH_INPUTLEN = Long.MAX_VALUE;
    private static final long UNSIGNED_INT_MAX_VALUE = 4294967295L;
    private static MessageDigest md;

    public ConcatKeyDerivationFunction(String hashAlg) throws NoSuchAlgorithmException {
        md = MessageDigest.getInstance(hashAlg);
    }

    public byte[] concatKDF(byte[] z, int keyDataLen, byte[] algorithmID, byte[] partyUInfo, byte[] partyVInfo, byte[] suppPubInfo, byte[] suppPrivInfo) {
        int hashLen = md.getDigestLength() * 8;

        if (keyDataLen % 8 != 0) {
            throw new IllegalArgumentException("keydatalen should be a multiple of 8");
        }

        if (keyDataLen > (long) hashLen * UNSIGNED_INT_MAX_VALUE) {
            throw new IllegalArgumentException("keydatalen is too large");
        }

        if (algorithmID == null || partyUInfo == null || partyVInfo == null) {
            throw new NullPointerException("Required parameter is null");
        }

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            baos.write(algorithmID);
            baos.write(partyUInfo);
            baos.write(partyVInfo);
            if (suppPubInfo != null) {
                baos.write(suppPubInfo);
            }
            if (suppPrivInfo != null) {
                baos.write(suppPrivInfo);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        byte[] otherInfo = baos.toByteArray();
        return concatKDF(z, keyDataLen, otherInfo);
    }

    private byte[] concatKDF(byte[] z, int keyDataLen, byte[] otherInfo) {
        keyDataLen = keyDataLen / 8;
        byte[] key = new byte[keyDataLen];

        int hashLen = md.getDigestLength();
        int reps = keyDataLen / hashLen;

        if (reps > UNSIGNED_INT_MAX_VALUE) {
            throw new IllegalArgumentException("Key derivation failed");
        }

        int counter = 1;
        byte[] counterInBytes = intToFourBytes(counter);

        if ((counterInBytes.length + z.length + otherInfo.length) * 8 > MAX_HASH_INPUTLEN) {
            throw new IllegalArgumentException("Key derivation failed");
        }

        for (int i = 0; i <= reps; i++) {
            md.reset();
            md.update(intToFourBytes(i + 1));
            md.update(z);
            md.update(otherInfo);

            byte[] hash = md.digest();
            if (i < reps) {
                System.arraycopy(hash, 0, key, hashLen * i, hashLen);
            } else {
                System.arraycopy(hash, 0, key, hashLen * i, keyDataLen % hashLen);
            }
        }
        return key;
    }

    private byte[] intToFourBytes(int i) {
        byte[] res = new byte[4];
        res[0] = (byte) (i >>> 24);
        res[1] = (byte) ((i >>> 16) & 0xFF);
        res[2] = (byte) ((i >>> 8) & 0xFF);
        res[3] = (byte) (i & 0xFF);
        return res;
    }
}

@Rasmus Faber: Thank you for your effort. I give you full credit for the above code. What I have done with the code above was to add in code to perform validation as required by the NIST specifications.

Also, I fixed a bug where the keyDataLen passed in was meant to specify the length in bits, but it was treated as the length in bytes. Hence, the key generated ended up being 8 times larger.

This was fixed by adding a line keyDataLen = keyDataLen/8; in the first line of the second method.

I thank everyone for their support and hope this piece of code will go a long way to the open source community!

Trews answered 4/6, 2012 at 10:14 Comment(8)
Why don't you feel the CNG implementation is usable?Decompensation
I was unable to match the parameters between the CNG implementation and the parameters defined in NIST SP 800-56A. For example, the CNG implementation does not even prompt for the hash algorithm to be used.Trews
The Microsoft implementation has the hash-function hardcoded, yes (and I can't see which hash-function it is hard-coded to). Does that make it unusable to you? Are you looking for another implementation in order to verify your own?Decompensation
I need an implementation where I can identify the hash-function. And yes, I need it to verify my own implementation. Also, the KDF is supposed to accept one secret as the input, but the Microsoft implementation asks for 2 secrets, which is confusing me as to how to use that implementationTrews
The Microsoft implementation is used by performing the DH/ECDH secret agreement scheme and then deriving a key from the secret. You cannot directly provide the secret, which I can see makes it more difficult to use it to compare results.Decompensation
Yes.. and in the NIST documentation, you provide the secret directly, which makes it difficult not just to compare results, but to use it as specified in the NIST documentation.Trews
@LaiXinChu do you by any chance have an implementation of ConcatKDF for iOS?Despicable
Fwiw the acvp program through NIST now allows testing against the one step kdf usnistgov.github.io/ACVP/…Unquestionable
D
9

Here is a quick and dirty implementation:

    public byte[] concatKDF(String hashAlg, byte[] z, int keyDataLen, byte[] algorithmID, byte[] partyUInfo, byte[] partyVInfo, byte[] suppPubInfo, byte[] suppPrivInfo) throws NoSuchAlgorithmException
{
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try {
        baos.write(algorithmID);
        baos.write(partyUInfo);
        baos.write(partyVInfo);
        baos.write(suppPubInfo);
        baos.write(suppPrivInfo);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }

    byte[] otherInfo = baos.toByteArray();
    return concatKDF(hashAlg, z, keyDataLen, otherInfo);
}

public byte[] concatKDF(String hashAlg, byte[] z, int keyDataLen, byte[] otherInfo) throws NoSuchAlgorithmException
{
    byte[] key = new byte[keyDataLen];
    MessageDigest md = MessageDigest.getInstance(hashAlg);
    int hashLen = md.getDigestLength(); 
    int reps = keyDataLen / hashLen;
    for(int i=1;i<=reps;i++){
        md.reset();
        md.update(intToFourBytes(i));
        md.update(z);
        md.update(otherInfo);
        byte[] hash = md.digest();
        if(i<reps){
            System.arraycopy(hash, 0, key, hashLen*(i-1), hashLen);
        }else{
            if(keyDataLen % hashLen == 0){
                System.arraycopy(hash, 0, key, hashLen*(i-1), hashLen);
            }else{
                System.arraycopy(hash, 0, key, hashLen*(i-1), keyDataLen % hashLen);
            }
        }
    }
    return key;
}

public byte[] intToFourBytes(int i){
    byte[] res = new byte[4];
    res[0] = (byte) (i >>> 24);
    res[1] = (byte) ((i >>> 16) & 0xFF);
    res[2] = (byte) ((i >>> 8) & 0xFF);
    res[3] = (byte) (i & 0xFF);
    return res;
}
Decompensation answered 10/6, 2012 at 18:47 Comment(5)
Hi thanks for your answer!! I tested your code thoroughly, and managed to match your implementation to mine, till we got the same output! I really appreciate you taking the time to implement this. I have added validation to your code, and fixed a bug with the keyDataLen as described above. Please feel free to relook the code again and let me know if you spot anything!Trews
Thanks once again! I will pass these codes to the community I am working with as we are currently in need of this ConcatKDF.Trews
@LaiXinChu: Note that your modified implementation does not correctly handle key-lengths that are not a multiple of 8 bits. I think I would just fail if keyDataLenInBits % 8 != 0.Decompensation
Yes good point. key-lengths should be a multiple of 8 bits for this implementation.Trews
Just a warning: This implementation does ONLY work with key out length of the same size of hash out length. Otherwise it will return 0bytes for every odd block.Carmella
A
3

Don't think you can find them other then test all on the list of Validated Component Implementations from http://csrc.nist.gov/groups/STM/cavp/documents/components/componentval.html.

There's only one supplier writing them in Java - Entrust http://www.entrust.com.

All of them are validated without the KDF :). The rest of work is up to you.

Ashur answered 7/6, 2012 at 15:10 Comment(2)
Sadly, all of them are validated without the KDF, and the KDF is exactly what I need! Hmm I will look into this. Thanks for the effort!Trews
I think this should be a comment, not an answer.Carmella
C
0

The newer NIST SP 800-56C rev1, which moved the definition of KDFs for key agreement protocols from NIST SP 800-56A, defines 2 different KDFs:

Single-Step KDF

Informally also referred to as "concat-kdf". Here is a standalone tested implementation of single-step kdf as described in NIST SP 800-56C rev1: https://github.com/patrickfav/singlestep-kdf

// a shared secret provided by your protocol
byte[] sharedSecret = ...
// a salt; if you don't have access to a salt, use salt-less SingleStepKdf.fromSha256() or similar
byte[] salt = ...
// other info to bind the key to the context, see the NIST spec for more detail
byte[] otherInfo = "macKey".getBytes();
byte[] keyMaterial = SingleStepKdf.fromHmacSha256().derive(sharedSecret, 32, salt, otherInfo);
SecretKey secretKey = new SecretKeySpec(keyMaterial, "AES");

OtherInfo vs FixedInfo

Compared to older 800-56A versions, the otherInfo paramter was renamed to fixedInfo. It can be used to facilitate the propitiatory message format for the various key agreement schemes as described in the SP.

Test Vectors

I couldn't find any official test vectors, so I released some here: https://github.com/patrickfav/singlestep-kdf/wiki/NIST-SP-800-56C-Rev1:-Non-Official-Test-Vectors

Here are some examples:

SHA-256

(z: afc4e154498d4770aa8365f6903dc83b, L: 16, fixedInfo: 662af20379b29d5ef813e655) = f0b80d6ae4c1e19e2105a37024e35dc6
(z: a3ce8d61d699ad150e196a7ab6736a63, L: 16, fixedInfo: ce5cd95a44ee83a8fb83f34c) = 5db3455a22b65edfcfde3da3e8d724cd
(z: a9723e56045f0847fdd9c1c78781c8b7, L: 16, fixedInfo: e69b6005b78f7d42d0a8ed2a) = ac3878b8cf357976f7fd8266923e1882
(z: a07a5e8df7ee1b2ce2a3d1348edfa8ab, L: 16, fixedInfo: e22a8ee34296dd39b56b31fb) = 70927d218b6d119268381e9930a4f256

(z: 3f892bd8b84dae64a782a35f6eaa8f00, L: 02, fixedInfo: ec3f1cd873d28858a58cc39e) = a7c0
(z: 3f892bd8b84dae64a782a35f6eaa8f00, L: 36, fixedInfo: ec3f1cd873d28858a58cc39e) = a7c0665298252531e0db37737a374651b368275f2048284d16a166c6d8a90a91a491c16f
(z: 3f892bd8b84dae64a782a35f6eaa8f00, L: 68, fixedInfo: ec3f1cd873d28858a58cc39e) = a7c0665298252531e0db37737a374651b368275f2048284d16a166c6d8a90a91a491c16f49641b9f516a03d9d6d0f4fe7b81ffdf1c816f40ecd74aed8eda2b8a3c714fa0

(z: 9ce5457e4a0eecc1c8709f7ef37a32e9, L: 16, fixedInfo: ) = 7d81e7d61acc06b90984ec4145469608

HMAC-SHA256

(z: 6ee6c00d70a6cd14bd5a4e8fcfec8386, L: 16, salt: 532f5131e0a2fecc722f87e5aa2062cb, fixedInfo: 861aa2886798231259bd0314) = 13479e9a91dd20fdd757d68ffe8869fb
(z: cb09b565de1ac27a50289b3704b93afd, L: 16, salt: d504c1c41a499481ce88695d18ae2e8f, fixedInfo: 5ed3768c2c7835943a789324) = f081c0255b0cae16edc6ce1d6c9d12bc
(z: 98f50345fd970639a1b7935f501e1d7c, L: 16, salt: 3691939461247e9f74382ae4ef629b17, fixedInfo: 6ddbdb1314663152c3ccc192) = 56f42183ed3e287298dbbecf143f51ac

(z: 02b40d33e3f685aeae677ac344eeaf77, L: 02, salt: 0ad52c9357c85e4781296a36ca72039c, fixedInfo: c67c389580128f18f6cf8592) = be32
(z: 02b40d33e3f685aeae677ac344eeaf77, L: 36, salt: 0ad52c9357c85e4781296a36ca72039c, fixedInfo: c67c389580128f18f6cf8592) = be32e7d306d891028be088f213f9f947c50420d9b5a12ca69818dd9995dedd8e6137c710
(z: 02b40d33e3f685aeae677ac344eeaf77, L: 68, salt: 0ad52c9357c85e4781296a36ca72039c, fixedInfo: c67c389580128f18f6cf8592) = be32e7d306d891028be088f213f9f947c50420d9b5a12ca69818dd9995dedd8e6137c7104d67f2ca90915dda0ab68af2f355b904f9eb0388b5b7fe193c9546d45849133d

(z: 2c2438b6321fed7a9eac200b91b3ac30, L: 56, salt: 6199187690823def2037e0632577c6b1, fixedInfo: ) = b402fda16e1c2719263be82158972c9080a7bafcbe0a3a6ede3504a3d5c8c0c0e00fe7e5f6bb3afdfa4d661b8fbe4bd7b950cfe0b2443bbd
(z: 0ffa4c40a822f6e3d86053aefe738eac, L: 64, salt: 6199187690823def2037e0632577c6b1, fixedInfo: ) = 0486d589aa71a603c09120fb76eeab3293eee2dc36a91b23eb954d6703ade8a7b660d920c5a6f7bf3898d0e81fbad3a680b74b33680e0cc6a16aa616d078b256
(z: a801d997ed539ae9aa05d17871eb7fab, L: 08, fixedInfo: 03697296e42a6fdbdb24b3ec) = 1a5efa3aca87c1f4
(z: e9624e112f9e90e7bf8a749cf37d920c, L: 16, fixedInfo: 03697296e42a6fdbdb24b3ec) = ee93ca3986cc43516ae4e29fd7a90ef1

Two-Step KDF

The most prominent implementation of two-step kdf is probably HKDF (RFC5869). A tested and standalone Java implementation can be found here: https://github.com/patrickfav/hkdf

Disclaimer: I am the developer of both libraries.

Carmella answered 23/11, 2018 at 9:24 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.