5 of 7
added 587 characters in body
ams
  • 351
  • 2
  • 9

How does the Concat KDF work?

I am writing an application using JSON Web Encryption with ECDH using the X25519 Curve. The RFC says that ECHD-ES derived secret is run through a Concat KDF.

Given that many cryptographers seem to be bashing JWT and the JOSE standard for having too many options to shoot yourself in the foot I want to make sure I am not using an insecure mode of JWE.

RFC 7518 JSON Web Algorithms (JWA) states

Key Agreement with Elliptic Curve Diffie-Hellman Ephemeral Static (ECDH-ES)

This section defines the specifics of key agreement with Elliptic
Curve Diffie-Hellman Ephemeral Static [RFC6090], in combination with
the Concat KDF, as defined in Section 5.8.1 of [NIST.800-56A].

Following the reference leads to 152 page NIST PDF that I don't fully understand. I am a developer not a cryptographer. I am using the Nimbus JOSE library in my application and it has an implementation of ContcatKDF.java derive function shown below.

Questions:

  • How does Contcat KDF work?
  • Is it considered secure?
/**
 * Derives a key from the specified inputs.
 *
 * @param sharedSecret  The shared secret. Must not be {@code null}.
 * @param keyLengthBits The length of the key to derive, in bits.
 * @param otherInfo     Other info, {@code null} if not specified.
 *
 * @return The derived key, with algorithm set to "AES".
 *
 * @throws JOSEException If the key derivation failed.
 */
public SecretKey deriveKey(final SecretKey sharedSecret,
                 final int keyLengthBits,
                 final byte[] otherInfo)
    throws JOSEException {

    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    final MessageDigest md = getMessageDigest();

    for (int i=1; i <= computeDigestCycles(ByteUtils.safeBitLength(md.getDigestLength()), keyLengthBits); i++) {

        byte[] counterBytes = IntegerUtils.toBytes(i);

        md.update(counterBytes);
        md.update(sharedSecret.getEncoded());

        if (otherInfo != null) {
            md.update(otherInfo);
        }

        try {
            baos.write(md.digest());
        } catch (IOException e) {
            throw new JOSEException("Couldn't write derived key: " + e.getMessage(), e);
        }
    }

    byte[] derivedKeyMaterial = baos.toByteArray();

    final int keyLengthBytes = ByteUtils.byteLength(keyLengthBits);

    if (derivedKeyMaterial.length == keyLengthBytes) {
        // Return immediately
        return new SecretKeySpec(derivedKeyMaterial, "AES");
    }

    return new SecretKeySpec(ByteUtils.subArray(derivedKeyMaterial, 0, keyLengthBytes), "AES");
}

It derive function gets called with SHA-256 as the hash function concatKDF = new ConcatKDF("SHA-256"); from ECDHCryptoProvider.java which computes the number of cycles using

/**
 * Computes the required digest (hashing) cycles for the specified
 * message digest length and derived key length.
 *
 * @param digestLengthBits The length of the message digest, in bits.
 * @param keyLengthBits    The length of the derived key, in bits.
 *
 * @return The digest cycles.
 */
public static int computeDigestCycles(final int digestLengthBits, final int keyLengthBits) {

    // return the ceiling of keyLength / digestLength
    
    return (keyLengthBits + digestLengthBits - 1) / digestLengthBits;
}
ams
  • 351
  • 2
  • 9