[java-opensaml] branch main updated: OSJ-333: Support encryption via classic Diffie-Hellman key agreement

Brent Putman putmanb at georgetown.edu
Wed Mar 10 01:37:53 UTC 2021


This is an automated email from the git hooks/post-receive script.

putmanb pushed a commit to branch main
in repository java-opensaml.

View the commit online:
http://git.shibboleth.net/view/?p=java-opensaml.git;a=commit;h=cd392dd71a292a156d739c78e5f27ecde3dc1127

The following commit(s) were added to refs/heads/main by this push:
       new  cd392dd71 OSJ-333: Support encryption via classic Diffie-Hellman key agreement
cd392dd71 is described below

commit cd392dd71a292a156d739c78e5f27ecde3dc1127
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Wed Mar 3 18:03:51 2021 -0500

    OSJ-333: Support encryption via classic Diffie-Hellman key agreement
    
    This includes key agreement, encryption, KeyInfo support and related
    components.
---
 .../saml/common/testing/SAMLTestSupport.java       |   2 +
 .../org/opensaml/security/crypto/JCAConstants.java |   8 +
 .../org/opensaml/security/crypto/KeySupport.java   |  31 ++++
 .../org/opensaml/security/crypto/dh/DHSupport.java | 135 +++++++++++++++
 .../opensaml/security/crypto/dh/package-info.java  |  19 +++
 .../opensaml/security/crypto/dh/BaseDHTest.java    |  37 ++++
 .../opensaml/security/crypto/dh/DHSupportTest.java | 108 ++++++++++++
 .../xmlsec/agreement/KeyAgreementSupport.java      |   2 +-
 .../opensaml/xmlsec/keyinfo/KeyInfoSupport.java    | 150 +++++++++++++++--
 .../org/opensaml/xmlsec/signature/KeyValue.java    |  21 +++
 .../DHWithExplicitKDFKeyAgreementProcessor.java    | 102 +++++++++++
 .../impl/DHWithLegacyKDFKeyAgreementProcessor.java | 146 ++++++++++++++++
 .../opensaml/xmlsec/agreement/impl/KANonce.java    | 111 +++++++++++-
 .../DefaultSecurityConfigurationBootstrap.java     |  14 ++
 .../xmlsec/derivation/impl/DHLegacyKDF.java        | 186 +++++++++++++++++++++
 .../xmlsec/signature/impl/KeyValueImpl.java        |  17 ++
 .../signature/impl/KeyValueUnmarshaller.java       |   3 +
 ...opensaml.xmlsec.agreement.KeyAgreementProcessor |   4 +-
 ...HWithExplicitKDFKeyAgreementProcessorTest.java} |  29 ++--
 ... DHWithLegacyKDFKeyAgreementProcessorTest.java} | 107 ++++++------
 .../impl/ECDHKeyAgreementProcessorTest.java        |   6 +-
 .../xmlsec/agreement/impl/KANonceTest.java         |  49 ++++--
 .../GlobalKeyAgreementProcessorRegistryTest.java   |  16 +-
 .../xmlsec/derivation/impl/ConcatKDFTest.java      |   2 -
 .../xmlsec/derivation/impl/DHLegacyKDFTest.java    | 157 +++++++++++++++++
 .../{ECDHTest.java => DHWithExplicitKDFTest.java}  |  46 +++--
 .../{ECDHTest.java => DHWithLegacyKDFTest.java}    |  86 ++++++----
 .../xmlsec/encryption/support/tests/ECDHTest.java  |  21 ++-
 .../impl/KeyAgreementKeyInfoGeneratorTest.java     | 138 ++++++++++++++-
 .../xmlsec/keyinfo/tests/KeyInfoSupportTest.java   | 114 ++++++++++++-
 .../xmlsec/testing/XMLSecurityTestingSupport.java  |   2 +
 .../src/test/resources/logback-test.xml            |   4 +
 32 files changed, 1708 insertions(+), 165 deletions(-)

diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/testing/SAMLTestSupport.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/testing/SAMLTestSupport.java
index 64fe0e684..7814c9202 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/testing/SAMLTestSupport.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/testing/SAMLTestSupport.java
@@ -23,6 +23,7 @@ import java.util.List;
 import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver;
 import org.opensaml.xmlsec.keyinfo.impl.BasicProviderKeyInfoCredentialResolver;
 import org.opensaml.xmlsec.keyinfo.impl.KeyInfoProvider;
+import org.opensaml.xmlsec.keyinfo.impl.provider.DEREncodedKeyValueProvider;
 import org.opensaml.xmlsec.keyinfo.impl.provider.DSAKeyValueProvider;
 import org.opensaml.xmlsec.keyinfo.impl.provider.ECKeyValueProvider;
 import org.opensaml.xmlsec.keyinfo.impl.provider.InlineX509DataProvider;
@@ -51,6 +52,7 @@ public final class SAMLTestSupport {
         providers.add( new RSAKeyValueProvider() );
         providers.add( new DSAKeyValueProvider() );
         providers.add( new ECKeyValueProvider() );
+        providers.add( new DEREncodedKeyValueProvider() );
         providers.add( new InlineX509DataProvider() );
         return providers;
     }
diff --git a/opensaml-security-api/src/main/java/org/opensaml/security/crypto/JCAConstants.java b/opensaml-security-api/src/main/java/org/opensaml/security/crypto/JCAConstants.java
index 329e220c7..13e14aae7 100644
--- a/opensaml-security-api/src/main/java/org/opensaml/security/crypto/JCAConstants.java
+++ b/opensaml-security-api/src/main/java/org/opensaml/security/crypto/JCAConstants.java
@@ -17,6 +17,8 @@
 
 package org.opensaml.security.crypto;
 
+import java.security.Key;
+
 /**
  * Various useful constants defined in and/or used with the Java Cryptography Architecture (JCA) specification.
  */
@@ -42,6 +44,12 @@ public final class JCAConstants {
     /** Key algorithm: "DESede". */
     public static final String KEY_ALGO_DESEDE = "DESede";
     
+    /** Key algorithm: "DH" (returned by {@link Key#getAlgorithm()}). */
+    public static final String KEY_ALGO_DH = "DH";
+    
+    /** Key algorithm: "DiffieHellman" (used with key and key pair factories, generators, etc). */
+    public static final String KEY_ALGO_DIFFIE_HELLMAN = "DiffieHellman";
+    
     
     
     // Key formats
diff --git a/opensaml-security-api/src/main/java/org/opensaml/security/crypto/KeySupport.java b/opensaml-security-api/src/main/java/org/opensaml/security/crypto/KeySupport.java
index 43044c97a..8b50f5b9a 100644
--- a/opensaml-security-api/src/main/java/org/opensaml/security/crypto/KeySupport.java
+++ b/opensaml-security-api/src/main/java/org/opensaml/security/crypto/KeySupport.java
@@ -57,6 +57,8 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
 import javax.crypto.spec.SecretKeySpec;
 
 import net.shibboleth.utilities.java.support.codec.Base64Support;
@@ -322,6 +324,19 @@ public final class KeySupport {
         final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(base64DecodeOrThrow(base64EncodedKey));
         return (DSAPublicKey) buildKey(keySpec, JCAConstants.KEY_ALGO_DSA);
     }
+    
+    /**
+     * Build Java DH public key from base64 encoding.
+     * 
+     * @param base64EncodedKey base64-encoded DH public key
+     * @return a native Java DHPublicKey
+     * @throws KeyException thrown if there is an error constructing key
+     */
+    @Nonnull public static DHPublicKey buildJavaDHPublicKey(@Nonnull final String base64EncodedKey)
+            throws KeyException {
+        final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(base64DecodeOrThrow(base64EncodedKey));
+        return (DHPublicKey) buildKey(keySpec, JCAConstants.KEY_ALGO_DIFFIE_HELLMAN);
+    }
 
     /**
      * Build Java RSA public key from base64 encoding.
@@ -397,6 +412,22 @@ public final class KeySupport {
         return (DSAPrivateKey) key;
     }
 
+    /**
+     * Build Java DH private key from base64 encoding.
+     * 
+     * @param base64EncodedKey base64-encoded DH private key
+     * @return a native Java DHPrivateKey
+     * @throws KeyException thrown if there is an error constructing key
+     */
+    @Nonnull public static DHPrivateKey buildJavaDHPrivateKey(@Nonnull final String base64EncodedKey)
+            throws KeyException {
+        final PrivateKey key = buildJavaPrivateKey(base64EncodedKey);
+        if (!(key instanceof DHPrivateKey)) {
+            throw new KeyException("Generated key was not a DHPrivateKey instance");
+        }
+        return (DHPrivateKey) key;
+    }
+
     /**
      * Build Java EC private key from base64 encoding.
      * 
diff --git a/opensaml-security-api/src/main/java/org/opensaml/security/crypto/dh/DHSupport.java b/opensaml-security-api/src/main/java/org/opensaml/security/crypto/dh/DHSupport.java
new file mode 100644
index 000000000..52b9fd216
--- /dev/null
+++ b/opensaml-security-api/src/main/java/org/opensaml/security/crypto/dh/DHSupport.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opensaml.security.crypto.dh;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.crypto.KeyAgreement;
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHParameterSpec;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.DomainParameters;
+import org.opensaml.security.crypto.JCAConstants;
+import org.opensaml.security.crypto.KeySupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Cryptography support related to Elliptic Curve.
+ */
+public final class DHSupport {
+    
+    /** Logger. */
+    private static final Logger LOG = LoggerFactory.getLogger(DHSupport.class);
+    
+    /** Constructor. */
+    private DHSupport() { }
+    
+    /**
+     * Perform DH key agreement between the given public and private keys.
+     * 
+     * @param publicKey the public key
+     * @param privateKey the private key
+     * @param provider the optional security provider to use
+     * 
+     * @return the secret produced by key agreement
+     * 
+     * @throws NoSuchAlgorithmException if algorithm is unknown
+     * @throws NoSuchProviderException if provider is unknown
+     * @throws InvalidKeyException if supplied key is invalid
+     */
+    public static byte[] performKeyAgreement(@Nonnull final DHPublicKey publicKey,
+            @Nonnull final DHPrivateKey privateKey, @Nullable final String provider)
+                    throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException {
+        Constraint.isNotNull(publicKey, "DHPublicKey was null");
+        Constraint.isNotNull(privateKey, "DHPrivateKey was null");
+        
+        KeyAgreement keyAgreement = null;
+        if (provider != null) {
+            keyAgreement = KeyAgreement.getInstance(JCAConstants.KEY_AGREEMENT_DH, provider);
+        } else {
+            keyAgreement = KeyAgreement.getInstance(JCAConstants.KEY_AGREEMENT_DH);
+        }
+        
+        keyAgreement.init(privateKey);
+        keyAgreement.doPhase(publicKey, true);
+        return keyAgreement.generateSecret();
+    }
+
+    /**
+     * Generate a key pair whose parameters are compatible with those of the specified DH public key.
+     * 
+     * @param publicKey the public key
+     * @param provider the optional security provider to use
+     * 
+     * @return the generated key pair
+     * 
+     * @throws NoSuchAlgorithmException if algorithm is unknown
+     * @throws NoSuchProviderException if provider is unknown
+     * @throws InvalidAlgorithmParameterException if the public key's {@link DHParameterSpec} is not supported
+     */
+    public static KeyPair generateCompatibleKeyPair(@Nonnull final DHPublicKey publicKey,
+            @Nullable final String provider)
+                    throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
+        Constraint.isNotNull(publicKey, "DHPublicKey was null");
+        
+        return KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN, publicKey.getParams(), provider);
+    }
+    
+    /**
+     * Obtain the prime Q domain parameter from the specified DH public key.
+     * 
+     * <p>
+     * Java's interface for DH domain parameters {@link DHParameterSpec} doesn't expose
+     * the prime Q parameter, but in some contexts it is required, e.g XML Encryption <code>DHKeyValue</code>
+     * element. The approach here is to parse the ASN.1 encoding of the key directly.
+     * </p>
+     * 
+     * @param publicKey the public key
+     * 
+     * @return the prime Q domain parameter, or null if could not be processed
+     */
+    public static BigInteger getPrimeQDomainParameter(@Nonnull final DHPublicKey publicKey) {
+        Constraint.isNotNull(publicKey, "DHPublicKey was null");
+        try (ASN1InputStream input = new ASN1InputStream(publicKey.getEncoded())) {
+            final SubjectPublicKeyInfo spki = SubjectPublicKeyInfo.getInstance(input.readObject());
+            if (spki.getAlgorithm().getParameters() != null) {
+                final DomainParameters dp = DomainParameters.getInstance(spki.getAlgorithm().getParameters());
+                return dp.getQ();
+            }
+            return null;
+        } catch (final Exception e) {
+            LOG.warn("Error processing DHPublicKey for prime Q parameter", e);
+            return null;
+        }
+    }
+
+} 
\ No newline at end of file
diff --git a/opensaml-security-api/src/main/java/org/opensaml/security/crypto/dh/package-info.java b/opensaml-security-api/src/main/java/org/opensaml/security/crypto/dh/package-info.java
new file mode 100644
index 000000000..f6f3750a6
--- /dev/null
+++ b/opensaml-security-api/src/main/java/org/opensaml/security/crypto/dh/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** Support for Diffie-Hellman cryptography. */
+package org.opensaml.security.crypto.dh;
\ No newline at end of file
diff --git a/opensaml-security-api/src/test/java/org/opensaml/security/crypto/dh/BaseDHTest.java b/opensaml-security-api/src/test/java/org/opensaml/security/crypto/dh/BaseDHTest.java
new file mode 100644
index 000000000..d6bf6b44a
--- /dev/null
+++ b/opensaml-security-api/src/test/java/org/opensaml/security/crypto/dh/BaseDHTest.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opensaml.security.crypto.dh;
+
+import org.opensaml.core.testing.OpenSAMLInitBaseTestCase;
+import org.testng.annotations.DataProvider;
+
+/**
+ * This mostly exists as a single place to define the set of DH key sizes we want to test in various tests.
+ */
+public class BaseDHTest extends OpenSAMLInitBaseTestCase {
+    
+    @DataProvider
+    public Object[][] dhKeySizes() {
+        return new Object[][] {
+            new Object[] {1024},
+            new Object[] {2048},
+            new Object[] {4096},
+        };
+    }
+
+}
diff --git a/opensaml-security-api/src/test/java/org/opensaml/security/crypto/dh/DHSupportTest.java b/opensaml-security-api/src/test/java/org/opensaml/security/crypto/dh/DHSupportTest.java
new file mode 100644
index 000000000..6edc2dc0a
--- /dev/null
+++ b/opensaml-security-api/src/test/java/org/opensaml/security/crypto/dh/DHSupportTest.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opensaml.security.crypto.dh;
+
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHPublicKeySpec;
+
+import org.opensaml.security.crypto.JCAConstants;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+/**
+ *
+ */
+public class DHSupportTest extends BaseDHTest {
+    
+    @Test(dataProvider="dhKeySizes")
+    public void generateCompatibleKeyPair(int keySize) throws Exception {
+        final KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN);
+        kpGenerator.initialize(keySize);
+        final KeyPair origKeyPair = kpGenerator.generateKeyPair();
+        Assert.assertNotNull(origKeyPair);
+        Assert.assertTrue(DHPublicKey.class.isInstance(origKeyPair.getPublic()));
+        DHPublicKey origPublicKey = DHPublicKey.class.cast(origKeyPair.getPublic());
+        
+        final KeyPair generatedKeyPair = DHSupport.generateCompatibleKeyPair(origPublicKey, null);
+        
+        Assert.assertNotNull(generatedKeyPair);
+        Assert.assertTrue(DHPublicKey.class.isInstance(generatedKeyPair.getPublic()));
+        Assert.assertTrue(DHPrivateKey.class.isInstance(generatedKeyPair.getPrivate()));
+    }
+    
+    @Test(dataProvider="dhKeySizes")
+    public void performKeyAgreement(int keySize) throws Exception {
+        final KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN);
+        kpGenerator.initialize(keySize);
+        final KeyPair publicKeyPair = kpGenerator.generateKeyPair();
+        DHPublicKey publicKey = DHPublicKey.class.cast(publicKeyPair.getPublic());
+        
+        final KeyPair privateKeyPair = DHSupport.generateCompatibleKeyPair(publicKey, null);
+        
+        final DHPrivateKey privateKey = DHPrivateKey.class.cast(privateKeyPair.getPrivate());
+        
+        byte[] secret = DHSupport.performKeyAgreement(publicKey, privateKey, null);
+        Assert.assertNotNull(secret);
+    }
+    
+    @Test(dataProvider="dhKeySizes")
+    public void getPrimeQDomainParameter(int keySize) throws Exception {
+        final KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN);
+        kpGenerator.initialize(keySize);
+        final KeyPair publicKeyPair = kpGenerator.generateKeyPair();
+        DHPublicKey publicKey1 = DHPublicKey.class.cast(publicKeyPair.getPublic());
+        
+        BigInteger q1 = DHSupport.getPrimeQDomainParameter(publicKey1);
+        Assert.assertNotNull(q1);
+        
+        // Check that a DHPublicKey constructed via a KeyFactory from G and P alone still has Q component
+        
+        final DHPublicKeySpec dhPubSpec = new DHPublicKeySpec(publicKey1.getY(), publicKey1.getParams().getP(), publicKey1.getParams().getG());
+        final KeyFactory keyFactory = KeyFactory.getInstance(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN);
+        final DHPublicKey publicKey2 = DHPublicKey.class.cast(keyFactory.generatePublic(dhPubSpec));
+        Assert.assertEquals(publicKey2, publicKey2);
+        
+        /* Turns out this doesn't actually hold. BC throws IllegalArgumentException on new instance of DomainParameters
+         * b/c ASN1Sequence has only 2 elements (P and G), not 3 as it should.
+         * Seems like a bug in Java, b/c Q is required in the ASN.1 encoding.
+         * See: https://tools.ietf.org/html/rfc3279, section 2.3.3.
+        BigInteger q2 = DHSupport.getPrimeQDomainParameter(publicKey2);
+        Assert.assertNotNull(q2);
+        Assert.assertEquals(q2, q1);
+        */
+        
+        // Check that a new DHPublicKey generated from the params spec of public key created with P and G alone,
+        // still has a Q component.  This does work, despite the issue with the input key itself.
+        // More reason to think the above is a bug, e.g. in KeyFactory.
+        
+        final KeyPairGenerator kpGenerator2 = KeyPairGenerator.getInstance(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN);
+        kpGenerator2.initialize(publicKey2.getParams());
+        final KeyPair compatKeyPair = kpGenerator.generateKeyPair();
+        final DHPublicKey publicKey3 = DHPublicKey.class.cast(compatKeyPair.getPublic());
+        
+        BigInteger q3 = DHSupport.getPrimeQDomainParameter(publicKey3);
+        Assert.assertNotNull(q3);
+        Assert.assertEquals(q3, q1);
+    }
+    
+}
diff --git a/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/agreement/KeyAgreementSupport.java b/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/agreement/KeyAgreementSupport.java
index f42925508..588823570 100644
--- a/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/agreement/KeyAgreementSupport.java
+++ b/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/agreement/KeyAgreementSupport.java
@@ -37,7 +37,7 @@ import org.opensaml.xmlsec.encryption.KeySize;
 public final class KeyAgreementSupport {
     
     /** JCA key algorithms that support key agreement. */
-    public static final Set<String> KEY_ALGORITHMS = Set.of(JCAConstants.KEY_ALGO_EC);
+    public static final Set<String> KEY_ALGORITHMS = Set.of(JCAConstants.KEY_ALGO_EC, JCAConstants.KEY_ALGO_DH);
     
     /** Constructor. */
     private KeyAgreementSupport() {}
diff --git a/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/keyinfo/KeyInfoSupport.java b/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/keyinfo/KeyInfoSupport.java
index cd39cd5da..f7624d1a9 100644
--- a/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/keyinfo/KeyInfoSupport.java
+++ b/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/keyinfo/KeyInfoSupport.java
@@ -47,11 +47,8 @@ import java.util.List;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
-
-import net.shibboleth.utilities.java.support.codec.Base64Support;
-import net.shibboleth.utilities.java.support.codec.DecodingException;
-import net.shibboleth.utilities.java.support.codec.EncodingException;
-import net.shibboleth.utilities.java.support.logic.Constraint;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHPublicKeySpec;
 
 import org.apache.xml.security.utils.XMLUtils;
 import org.opensaml.core.xml.XMLObjectBuilder;
@@ -60,9 +57,13 @@ import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
 import org.opensaml.security.SecurityException;
 import org.opensaml.security.credential.Credential;
 import org.opensaml.security.crypto.JCAConstants;
+import org.opensaml.security.crypto.dh.DHSupport;
 import org.opensaml.security.crypto.ec.ECSupport;
 import org.opensaml.security.x509.X509Support;
 import org.opensaml.xmlsec.algorithm.AlgorithmSupport;
+import org.opensaml.xmlsec.encryption.DHKeyValue;
+import org.opensaml.xmlsec.encryption.Generator;
+import org.opensaml.xmlsec.encryption.Public;
 import org.opensaml.xmlsec.signature.DEREncodedKeyValue;
 import org.opensaml.xmlsec.signature.DSAKeyValue;
 import org.opensaml.xmlsec.signature.ECKeyValue;
@@ -89,6 +90,11 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Strings;
 
+import net.shibboleth.utilities.java.support.codec.Base64Support;
+import net.shibboleth.utilities.java.support.codec.DecodingException;
+import net.shibboleth.utilities.java.support.codec.EncodingException;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
 /**
  * Utility class for working with data inside a KeyInfo object.
  * 
@@ -515,11 +521,18 @@ public class KeyInfoSupport {
     }    
     
     /**
-     * Converts a Java DSA or RSA public key into the corresponding XMLObject and stores it in a {@link KeyInfo} in a
-     * new {@link KeyValue} element.
+     * Converts a Java RSA, EC, DSA or DH public key into the corresponding XMLObject and stores it in a
+     * {@link KeyInfo} in a new {@link KeyValue} element.
      * 
-     * As input, only supports {@link PublicKey}s which are instances of either
-     * {@link java.security.interfaces.DSAPublicKey} or {@link java.security.interfaces.RSAPublicKey}
+     * <p>
+     * As input, only supports {@link PublicKey} instances which are:
+     * </p>
+     * <ul>
+     * <li>{@link java.security.interfaces.RSAPublicKey}</li>
+     * <li>{@link java.security.interfaces.ECPublicKey}</li>
+     * <li>{@link java.security.interfaces.DSAPublicKey}</li>
+     * <li>{@link javax.crypto.interfaces.DHPublicKey}</li>
+     * </ul>
      * 
      * @param keyInfo the {@link KeyInfo} element to which to add the key
      * @param pk the native Java {@link PublicKey} to add
@@ -539,12 +552,68 @@ public class KeyInfoSupport {
             keyValue.setECKeyValue(buildECKeyValue((ECPublicKey) pk));
         } else if (pk instanceof DSAPublicKey) {
             keyValue.setDSAKeyValue(buildDSAKeyValue((DSAPublicKey) pk));
+        } else if (pk instanceof DHPublicKey) {
+            keyValue.setDHKeyValue(buildDHKeyValue((DHPublicKey) pk));
         } else {
-            throw new IllegalArgumentException("Only RSAPublicKey and DSAPublicKey are supported");
+            throw new IllegalArgumentException("Saw unsupported public key type: " + pk.getClass().getName());
         }
 
         keyInfo.getKeyValues().add(keyValue);
     }
+    
+    /**
+     * Builds a {@link DHKeyValue} XMLObject from the Java security DH public key type.
+     * 
+     * @param dhPubKey a native Java {@link DHPublicKey}
+     * @return an {@link DHKeyValue} XMLObject
+     * @throws EncodingException if the DH public key parameters can not be base64 encoded
+     */
+    @Nonnull public static DHKeyValue buildDHKeyValue(@Nonnull final DHPublicKey dhPubKey) 
+            throws EncodingException {
+        Constraint.isNotNull(dhPubKey, "DH public key cannot be null");
+        
+        final XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory();
+
+        final XMLObjectBuilder<DHKeyValue> dhKeyValueBuilder =
+                builderFactory.getBuilderOrThrow(DHKeyValue.DEFAULT_ELEMENT_NAME);
+        final DHKeyValue dhKeyValue = dhKeyValueBuilder.buildObject(DHKeyValue.DEFAULT_ELEMENT_NAME);
+
+        final XMLObjectBuilder<Generator> generatorBuilder =
+                builderFactory.getBuilderOrThrow(Generator.DEFAULT_ELEMENT_NAME);
+        final XMLObjectBuilder<Public> publicBuilder = builderFactory.getBuilderOrThrow(Public.DEFAULT_ELEMENT_NAME);
+        final XMLObjectBuilder<org.opensaml.xmlsec.encryption.P> pBuilder =
+                builderFactory.getBuilderOrThrow(org.opensaml.xmlsec.encryption.P.DEFAULT_ELEMENT_NAME);
+        final XMLObjectBuilder<org.opensaml.xmlsec.encryption.Q> qBuilder =
+                builderFactory.getBuilderOrThrow(org.opensaml.xmlsec.encryption.Q.DEFAULT_ELEMENT_NAME);
+        
+        final Public pub = publicBuilder.buildObject(Public.DEFAULT_ELEMENT_NAME);
+        final Generator gen = generatorBuilder.buildObject(Generator.DEFAULT_ELEMENT_NAME);
+        final org.opensaml.xmlsec.encryption.P p =
+                pBuilder.buildObject(org.opensaml.xmlsec.encryption.P.DEFAULT_ELEMENT_NAME);
+
+        pub.setValueBigInt(dhPubKey.getY());
+        dhKeyValue.setPublic(pub);
+
+        gen.setValueBigInt(dhPubKey.getParams().getG());
+        dhKeyValue.setGenerator(gen);
+
+        p.setValueBigInt(dhPubKey.getParams().getP());
+        dhKeyValue.setP(p);
+
+        // DHParameterSpec doesn't expose the Q param.  Also, it seems sometimes the ASN.1 encoded keys do not have
+        // a Q param, which is a violation of RFC 3279, section 2.3.3. So we have to deal with the null case.
+        // If it doesn't have Q, just emit without, even thought it violates the XML Encryption schema,
+        // since we don't actually need it to construct a DHPublicKey key anyway.
+        final BigInteger qValue = DHSupport.getPrimeQDomainParameter(dhPubKey);
+        if (qValue != null) {
+            final org.opensaml.xmlsec.encryption.Q q =
+                    qBuilder.buildObject(org.opensaml.xmlsec.encryption.Q.DEFAULT_ELEMENT_NAME);
+            q.setValueBigInt(qValue);
+            dhKeyValue.setQ(q);
+        }
+
+        return dhKeyValue;
+    }
 
     /**
      * Builds an {@link ECKeyValue} XMLObject from the Java security EC public key type.
@@ -751,6 +820,8 @@ public class KeyInfoSupport {
             return getRSAKey(keyValue.getRSAKeyValue());
         } else if (keyValue.getECKeyValue() != null) {
             return getECKey(keyValue.getECKeyValue());
+        } else if (keyValue.getDHKeyValue() != null) {
+            return getDHKey(keyValue.getDHKeyValue());
         } else {
             return null;
         }
@@ -792,6 +863,52 @@ public class KeyInfoSupport {
         }
         
     }
+    
+    /**
+     * Builds a DH key from a {@link DHKeyValue} element. The element must contain values for all required DH public
+     * key parameters, including values for shared key family values P, Q and G (aka Generator).
+     * 
+     * @param keyDescriptor the {@link DHKeyValue} key descriptor
+     * 
+     * @return a new {@link DHPublicKey} instance of {@link PublicKey}
+     * 
+     * @throws KeyException thrown if the key algorithm is not supported by the JCE or the key spec does not contain
+     *             valid information
+     */
+    @Nonnull public static PublicKey getDHKey(@Nonnull final DHKeyValue keyDescriptor) throws KeyException {
+        if (!hasCompleteDHParams(keyDescriptor)) {
+            throw new KeyException("DHKeyValue element did not contain at least one of DH parameters P, Q or G");
+        }
+
+        final BigInteger gComponent = keyDescriptor.getGenerator().getValueBigInt();
+        final BigInteger pComponent = keyDescriptor.getP().getValueBigInt();
+        // Note: Java doesn't need or even accept the prime Q component, so don't bother to parse it
+
+        final BigInteger publicComponent = keyDescriptor.getPublic().getValueBigInt();
+
+        final DHPublicKeySpec keySpec = new DHPublicKeySpec(publicComponent, pComponent, gComponent);
+        return buildKey(keySpec, JCAConstants.KEY_ALGO_DIFFIE_HELLMAN);
+    }
+    
+    /**
+     * Check whether the specified {@link DHKeyValue} element has the all optional DH values which can be shared
+     * amongst many keys in a DH "key family", and are presumed to be known from context.
+     * 
+     * @param keyDescriptor the {@link DHKeyValue} element to check
+     * @return true if all parameters are present and non-empty, false otherwise
+     */
+    public static boolean hasCompleteDHParams(@Nullable final DHKeyValue keyDescriptor) {
+        if (keyDescriptor == null
+                || keyDescriptor.getGenerator() == null
+                || Strings.isNullOrEmpty(keyDescriptor.getGenerator().getValue())
+                || keyDescriptor.getP() == null || Strings.isNullOrEmpty(keyDescriptor.getP().getValue())
+                // Note: Java doesn't need or even accept the prime Q component.  So even though it's
+                // required per the schema, relax the check here and don't require.
+                ) {
+            return false;
+        }
+        return true;
+    }
 
     /**
      * Builds an DSA key from a {@link DSAKeyValue} element. The element must contain values for all required DSA public
@@ -943,10 +1060,15 @@ public class KeyInfoSupport {
      * @throws KeyException thrown if the given key data can not be converted into {@link PublicKey}
      */
     @Nonnull public static PublicKey getKey(@Nonnull final DEREncodedKeyValue keyValue) throws KeyException{
+        // Note: Testing shows DH must come before DSA.  If you attempt to decode a DH key as DSA,
+        // it "works", b/c they have similar structures, but it's probably not correct.  DSA does not decode as DH,
+        // so this ordering is what works at present. If this methodology has a problem in the future, we'll likely
+        // need to switch to explicit ASN.1 parsing of the key's type, instead of "try and return what doesn't fail".
         final String[] supportedKeyTypes = {
                 JCAConstants.KEY_ALGO_RSA,
-                JCAConstants.KEY_ALGO_DSA,
-                JCAConstants.KEY_ALGO_EC};
+                JCAConstants.KEY_ALGO_EC,
+                JCAConstants.KEY_ALGO_DIFFIE_HELLMAN,
+                JCAConstants.KEY_ALGO_DSA};
         
         Constraint.isNotNull(keyValue, "DEREncodedKeyValue cannot be null");
         if (keyValue.getValue() == null) {
@@ -961,15 +1083,17 @@ public class KeyInfoSupport {
 
         // Iterate over the supported key types until one produces a public key.
         for (final String keyType : supportedKeyTypes) {
+            getLogger().trace("Attempting to decode DER key as type: {}", keyType);
             try {
                 final KeyFactory keyFactory = KeyFactory.getInstance(keyType);
                 final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedKey);
                 final PublicKey publicKey = keyFactory.generatePublic(keySpec);
                 if (publicKey != null) {
+                    getLogger().trace("DER key decoded successfully as type: {}", keyType);
                     return publicKey;
                 }
             } catch (final NoSuchAlgorithmException | InvalidKeySpecException e) {
-                // Do nothing, try the next type
+                getLogger().trace("DER key failed decoding as: {}", keyType);
             }
         }
         throw new KeyException("DEREncodedKeyValue did not contain a supported key type");
diff --git a/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/signature/KeyValue.java b/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/signature/KeyValue.java
index 0acc86627..c0776ab5c 100644
--- a/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/signature/KeyValue.java
+++ b/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/signature/KeyValue.java
@@ -21,6 +21,7 @@ import javax.annotation.Nullable;
 import javax.xml.namespace.QName;
 
 import org.opensaml.core.xml.XMLObject;
+import org.opensaml.xmlsec.encryption.DHKeyValue;
 import org.opensaml.xmlsec.signature.support.SignatureConstants;
 
 /**
@@ -84,6 +85,26 @@ public interface KeyValue extends XMLObject {
      */
     public void setECKeyValue(@Nullable final ECKeyValue newECKeyValue);
     
+    /**
+     * Get the DHKeyValue child element.
+     * 
+     * @return DHKeyValue child element
+     */
+    @Nullable public default DHKeyValue getDHKeyValue() {
+        //TODO remove 'default' and method body in next major version (v5)
+        throw new UnsupportedOperationException("Method not implemented");
+    }
+
+    /**
+     * Set the DHKeyValue child element.
+     * 
+     * @param newDHKeyValue the new DHKeyValue child element
+     */
+    public default void setDHKeyValue(@Nullable final DHKeyValue newDHKeyValue) {
+        //TODO remove 'default' and method body in next major version (v5)
+        throw new UnsupportedOperationException("Method not implemented");
+    }
+
     /**
      * Get the wildcard <any> XMLObject child element.
      * 
diff --git a/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/agreement/impl/DHWithExplicitKDFKeyAgreementProcessor.java b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/agreement/impl/DHWithExplicitKDFKeyAgreementProcessor.java
new file mode 100644
index 000000000..493b86016
--- /dev/null
+++ b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/agreement/impl/DHWithExplicitKDFKeyAgreementProcessor.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opensaml.xmlsec.agreement.impl;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+
+import javax.annotation.Nonnull;
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+
+import org.opensaml.security.credential.BasicCredential;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.crypto.dh.DHSupport;
+import org.opensaml.xmlsec.agreement.KeyAgreementException;
+import org.opensaml.xmlsec.agreement.KeyAgreementParameters;
+import org.opensaml.xmlsec.agreement.KeyAgreementProcessor;
+import org.opensaml.xmlsec.encryption.support.EncryptionConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of {@link KeyAgreementProcessor} which performs Diffie-Hellman
+ * Ephemeral-Static Mode key agreement with Explicit Key Derivation Function as defined in XML Encryption 1.1.
+ */
+public class DHWithExplicitKDFKeyAgreementProcessor extends AbstractDerivationKeyAgreementProcessor {
+    
+    /** Logger. */
+    private final Logger log = LoggerFactory.getLogger(DHWithExplicitKDFKeyAgreementProcessor.class);
+
+    /** {@inheritDoc} */
+    public String getAlgorithm() {
+        return EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH_EXPLICIT_KDF;
+    }
+
+    /** {@inheritDoc} */
+    protected Credential obtainPrivateCredential(@Nonnull final Credential publicCredential,
+            @Nonnull final KeyAgreementParameters parameters) throws KeyAgreementException {
+        
+        final Credential suppliedCredential = super.obtainPrivateCredential(publicCredential, parameters);
+        if (suppliedCredential != null) {
+            return suppliedCredential;
+        }
+        
+        log.debug("Found no supplied PrivateCredential in KeyAgreementParameters, generating ephemeral key pair");
+        
+        
+        if (!DHPublicKey.class.isInstance(publicCredential.getPublicKey())) {
+            throw new KeyAgreementException("Public credential's public key is not an instance of DHPublicKey");
+        }
+        
+        final DHPublicKey publicKey = DHPublicKey.class.cast(publicCredential.getPublicKey());
+        
+        try {
+            final KeyPair privateKeyPair = DHSupport.generateCompatibleKeyPair(publicKey, null);
+            return new BasicCredential(privateKeyPair.getPublic(), privateKeyPair.getPrivate());
+        } catch (final NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
+            throw new KeyAgreementException("Error generating private KeyPair from DH public key", e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    protected byte[] generateAgreementSecret(@Nonnull final Credential publicCredential,
+            @Nonnull final Credential privateCredential, @Nonnull final KeyAgreementParameters parameters)
+                    throws KeyAgreementException {
+        
+        if (!DHPublicKey.class.isInstance(publicCredential.getPublicKey())) {
+            throw new KeyAgreementException("Public credential's public key is not an instance of DHPublicKey");
+        }
+        if (!DHPrivateKey.class.isInstance(privateCredential.getPrivateKey())) {
+            throw new KeyAgreementException("Private credential's private key is not an instance of DHPrivateKey");
+        }
+        
+        final DHPublicKey publicKey = DHPublicKey.class.cast(publicCredential.getPublicKey());
+        final DHPrivateKey privateKey = DHPrivateKey.class.cast(privateCredential.getPrivateKey());
+        
+        try {
+            return DHSupport.performKeyAgreement(publicKey, privateKey, null);
+        } catch (final InvalidKeyException | NoSuchAlgorithmException | NoSuchProviderException e) {
+            throw new KeyAgreementException("Error generating secret from public and private DH keys", e);
+        }
+    }
+
+}
diff --git a/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/agreement/impl/DHWithLegacyKDFKeyAgreementProcessor.java b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/agreement/impl/DHWithLegacyKDFKeyAgreementProcessor.java
new file mode 100644
index 000000000..a8638fbc0
--- /dev/null
+++ b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/agreement/impl/DHWithLegacyKDFKeyAgreementProcessor.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opensaml.xmlsec.agreement.impl;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+
+import javax.annotation.Nonnull;
+import javax.crypto.SecretKey;
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+
+import org.opensaml.security.credential.BasicCredential;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.crypto.dh.DHSupport;
+import org.opensaml.xmlsec.agreement.KeyAgreementException;
+import org.opensaml.xmlsec.agreement.KeyAgreementParameters;
+import org.opensaml.xmlsec.agreement.KeyAgreementProcessor;
+import org.opensaml.xmlsec.agreement.KeyAgreementSupport;
+import org.opensaml.xmlsec.derivation.KeyDerivationException;
+import org.opensaml.xmlsec.derivation.impl.DHLegacyKDF;
+import org.opensaml.xmlsec.encryption.support.EncryptionConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of {@link KeyAgreementProcessor} which performs Diffie-Hellman
+ * Ephemeral-Static Mode key agreement with Legacy Key Derivation Function as defined in XML Encryption 1.1.
+ */
+public class DHWithLegacyKDFKeyAgreementProcessor extends AbstractKeyAgreementProcessor {
+    
+    /** Default digest method. */
+    public static final String DEFAULT_DIGEST_METHOD = EncryptionConstants.ALGO_ID_DIGEST_SHA256;
+    
+    /** Logger. */
+    private final Logger log = LoggerFactory.getLogger(DHWithLegacyKDFKeyAgreementProcessor.class);
+
+    /** {@inheritDoc} */
+    public String getAlgorithm() {
+        return EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH;
+    }
+
+    /** {@inheritDoc} */
+    protected Credential obtainPrivateCredential(@Nonnull final Credential publicCredential,
+            @Nonnull final KeyAgreementParameters parameters) throws KeyAgreementException {
+        
+        final Credential suppliedCredential = super.obtainPrivateCredential(publicCredential, parameters);
+        if (suppliedCredential != null) {
+            return suppliedCredential;
+        }
+        
+        log.debug("Found no supplied PrivateCredential in KeyAgreementParameters, generating ephemeral key pair");
+        
+        
+        if (!DHPublicKey.class.isInstance(publicCredential.getPublicKey())) {
+            throw new KeyAgreementException("Public credential's public key is not an instance of DHPublicKey");
+        }
+        
+        final DHPublicKey publicKey = DHPublicKey.class.cast(publicCredential.getPublicKey());
+        
+        try {
+            final KeyPair privateKeyPair = DHSupport.generateCompatibleKeyPair(publicKey, null);
+            return new BasicCredential(privateKeyPair.getPublic(), privateKeyPair.getPrivate());
+        } catch (final NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
+            throw new KeyAgreementException("Error generating private KeyPair from DH public key", e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    protected byte[] generateAgreementSecret(@Nonnull final Credential publicCredential,
+            @Nonnull final Credential privateCredential, @Nonnull final KeyAgreementParameters parameters)
+                    throws KeyAgreementException {
+        
+        if (!DHPublicKey.class.isInstance(publicCredential.getPublicKey())) {
+            throw new KeyAgreementException("Public credential's public key is not an instance of DHPublicKey");
+        }
+        if (!DHPrivateKey.class.isInstance(privateCredential.getPrivateKey())) {
+            throw new KeyAgreementException("Private credential's private key is not an instance of DHPrivateKey");
+        }
+        
+        final DHPublicKey publicKey = DHPublicKey.class.cast(publicCredential.getPublicKey());
+        final DHPrivateKey privateKey = DHPrivateKey.class.cast(privateCredential.getPrivateKey());
+        
+        try {
+            return DHSupport.performKeyAgreement(publicKey, privateKey, null);
+        } catch (final InvalidKeyException | NoSuchAlgorithmException | NoSuchProviderException e) {
+            throw new KeyAgreementException("Error generating secret from public and private DH keys", e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    protected SecretKey deriveSecretKey(final byte[] secret, @Nonnull final String keyAlgorithm,
+            @Nonnull final KeyAgreementParameters parameters) throws KeyAgreementException {
+        
+        final Integer keySize = parameters.contains(KeySize.class) ? parameters.get(KeySize.class).getSize() : null;
+        
+        KeyAgreementSupport.validateKeyAlgorithmAndSize(keyAlgorithm, keySize);
+        
+        String digestMethod = null;
+        if (parameters.contains(DigestMethod.class)) {
+            digestMethod = parameters.get(DigestMethod.class).getAlgorithm();
+        } else {
+            digestMethod = DEFAULT_DIGEST_METHOD;
+            // Need to add this to params so can be expressed on credential and in XML
+            final DigestMethod dm = new DigestMethod();
+            dm.setAlgorithm(digestMethod);
+            parameters.add(dm);
+        }
+        
+        // Nonce is optional
+        String nonce = null;
+        if (parameters.contains(KANonce.class)) {
+            nonce = parameters.get(KANonce.class).getValue();
+        }
+        
+        final DHLegacyKDF kdf = new DHLegacyKDF();
+        kdf.setDigestMethod(digestMethod);
+        kdf.setNonce(nonce);
+        
+        try {
+            return kdf.derive(secret, keyAlgorithm, keySize);
+        } catch (final KeyDerivationException e) {
+            throw new KeyAgreementException("Key derivation failed using supplied KeyDerivation parameter", e);
+        }
+        
+    }
+
+}
diff --git a/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/agreement/impl/KANonce.java b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/agreement/impl/KANonce.java
index 9b317cc72..3a7715dab 100644
--- a/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/agreement/impl/KANonce.java
+++ b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/agreement/impl/KANonce.java
@@ -17,16 +17,23 @@
 
 package org.opensaml.xmlsec.agreement.impl;
 
+import java.security.SecureRandom;
+
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.opensaml.core.xml.XMLObject;
+import org.opensaml.core.xml.XMLRuntimeException;
 import org.opensaml.core.xml.util.XMLObjectSupport;
 import org.opensaml.xmlsec.agreement.CloneableKeyAgreementParameter;
 import org.opensaml.xmlsec.agreement.KeyAgreementException;
 import org.opensaml.xmlsec.agreement.KeyAgreementParameter;
 import org.opensaml.xmlsec.agreement.XMLExpressableKeyAgreementParameter;
 
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.codec.Base64Support;
+import net.shibboleth.utilities.java.support.codec.DecodingException;
+import net.shibboleth.utilities.java.support.codec.EncodingException;
 import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
@@ -39,22 +46,27 @@ import net.shibboleth.utilities.java.support.primitive.StringSupport;
 public class KANonce extends AbstractInitializableComponent
     implements XMLExpressableKeyAgreementParameter, CloneableKeyAgreementParameter {
     
+    /** Default length for generated salt, in bytes. */
+    public static final Integer DEFAULT_GENERATED_LENGTH = 8;
+    
     /** Base64-encoded nonce value. */
     @Nullable private String value;
     
-    /** {@inheritDoc} */
-    protected void doInitialize() throws ComponentInitializationException {
-        if (value == null) {
-            throw new ComponentInitializationException("KANonce value was null");
-        }
-    }
-
+    /** Generated salt length, in bytes. */
+    @NonnullAfterInit private Integer generatedLength;
+    
+    /** SecureRandom generator for salt. */
+    @NonnullAfterInit private SecureRandom secureRandom;
+    
     /**
      * Get the Base64-encoded nonce value.
      * 
      * @return the nonce value
      */
     @Nullable public String getValue() {
+        if (value == null && isInitialized()) {
+            value = generateValue();
+        }
         return value;
     }
     
@@ -67,6 +79,87 @@ public class KANonce extends AbstractInitializableComponent
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         value = StringSupport.trimOrNull(newValue);
     }
+    
+    /**
+     * Get the generated length, in bytes.
+     * 
+     * @return the generated length, in bytes
+     */
+    @NonnullAfterInit public Integer getGeneratedLength() {
+        return generatedLength;
+    }
+    
+    /**
+     * Set the generated length, in bytes.
+     * 
+     * @param length the generated length
+     */
+    public void setGeneratedLength(@Nullable final Integer length) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        generatedLength = length;
+    }
+    
+    /**
+     * Get the secure random generator.
+     * 
+     * <p>
+     * Defaults to the platform default via <code>new SecureRandom()</code>
+     * </p>
+     * 
+     * @return the secure random instance
+     */
+    @NonnullAfterInit public SecureRandom getRandom() {
+        return secureRandom;
+    }
+    
+    /**
+     * Set the secure random generator.
+     * 
+     * <p>
+     * Defaults to the platform default via <code>new SecureRandom()</code>
+     * </p>
+     * 
+     * @param sr the secure random generator to set
+     */
+    public void setRandom(@Nullable final SecureRandom sr) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        secureRandom = sr;
+    }
+
+    /** {@inheritDoc} */
+    protected void doInitialize() throws ComponentInitializationException {
+        if (value != null) {
+            try {
+                Base64Support.decode(value);
+            } catch (final DecodingException e) {
+                throw new ComponentInitializationException("Nonce value was not valid Base64", e);
+            }
+        }
+        
+        if (generatedLength == null) {
+            generatedLength = DEFAULT_GENERATED_LENGTH;
+        }
+        
+        if (secureRandom == null) {
+            secureRandom = new SecureRandom();
+        }
+    }
+    
+    /**
+     * Generate a new random value.
+     * 
+     * @return the generated value
+     */
+    protected String generateValue() {
+        try {
+            final byte[] valueBytes = new byte[generatedLength];
+            secureRandom.nextBytes(valueBytes);
+            return Base64Support.encode(valueBytes, false);
+        } catch (final EncodingException e) {
+            // This should never really happen
+            throw new XMLRuntimeException("Error Base64-encoding generated nonce value salt", e);
+        }
+    }
 
     /** {@inheritDoc} */
     public KANonce clone() {
@@ -102,6 +195,10 @@ public class KANonce extends AbstractInitializableComponent
             throws ComponentInitializationException {
         Constraint.isNotNull(xmlObject, "XMLObject was null");
         
+        if (StringSupport.trimOrNull(xmlObject.getValue()) == null) {
+            throw new ComponentInitializationException("XML KANonce had a null or empty value");
+        }
+        
         final KANonce parameter = new KANonce();
         parameter.setValue(xmlObject.getValue());
         parameter.initialize();
diff --git a/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/config/impl/DefaultSecurityConfigurationBootstrap.java b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/config/impl/DefaultSecurityConfigurationBootstrap.java
index 1dbbd939c..d21a30df8 100644
--- a/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/config/impl/DefaultSecurityConfigurationBootstrap.java
+++ b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/config/impl/DefaultSecurityConfigurationBootstrap.java
@@ -27,6 +27,8 @@ import java.util.Set;
 import javax.annotation.Nonnull;
 
 import org.opensaml.security.crypto.JCAConstants;
+import org.opensaml.xmlsec.agreement.impl.DigestMethod;
+import org.opensaml.xmlsec.agreement.impl.KANonce;
 import org.opensaml.xmlsec.derivation.impl.ConcatKDF;
 import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver;
 import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver;
@@ -126,6 +128,18 @@ public class DefaultSecurityConfigurationBootstrap {
             ecConfig.setParameters(Set.of(ecConcatKDF));
             kaConfigs.put(JCAConstants.KEY_ALGO_EC, ecConfig);
             
+            // For DH we default the Legacy KDF variant as that is mandatory for DH support.
+            final KeyAgreementEncryptionConfiguration dhConfig = new KeyAgreementEncryptionConfiguration();
+            dhConfig.setAlgorithm(EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH);
+            final DigestMethod digestMethod = new DigestMethod();
+            digestMethod.setAlgorithm(EncryptionConstants.ALGO_ID_DIGEST_SHA256);
+            digestMethod.initialize();
+            KANonce nonce = new KANonce();
+            // This will use an auto-generated nonce value each time
+            nonce.initialize();
+            dhConfig.setParameters(Set.of(digestMethod, nonce));
+            kaConfigs.put(JCAConstants.KEY_ALGO_DH, dhConfig);
+            
             config.setKeyAgreementConfigurations(kaConfigs);
         } catch (final ComponentInitializationException e) {
             LOG.error("Initialization failure on global key agreement encryption configuration, will be unusable", e);
diff --git a/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/derivation/impl/DHLegacyKDF.java b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/derivation/impl/DHLegacyKDF.java
new file mode 100644
index 000000000..889b894c7
--- /dev/null
+++ b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/derivation/impl/DHLegacyKDF.java
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opensaml.xmlsec.derivation.impl;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.commons.codec.binary.Hex;
+import org.opensaml.xmlsec.algorithm.AlgorithmSupport;
+import org.opensaml.xmlsec.derivation.KeyDerivationException;
+import org.opensaml.xmlsec.derivation.KeyDerivationSupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Charsets;
+import com.google.common.primitives.Bytes;
+
+import net.shibboleth.utilities.java.support.codec.Base64Support;
+import net.shibboleth.utilities.java.support.codec.DecodingException;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/**
+ * Implementation of the key derivation function used with Diffie-Hellman Key Agreement With Legacy Key Derivation
+ * Function as defined in XML Encryption 1.1.
+ */
+public class DHLegacyKDF {
+    
+    /** Logger. */
+    private final Logger log = LoggerFactory.getLogger(DHLegacyKDF.class);
+    
+    /** Digest method. */
+    @Nullable private String digestMethod;
+    
+    /** Nonce. */
+    @Nullable private String nonce;
+
+    /**
+     * Get the digest method algorithm URI.
+     * 
+     * @return the algorithm URI
+     */
+    @Nullable public String getDigestMethod() {
+        return digestMethod;
+    }
+
+    /**
+     * Set the digest method algorithm URI.
+     * 
+     * @param newDigestMethod the algorithm URI
+     */
+    public void setDigestMethod(@Nullable final String newDigestMethod) {
+        digestMethod = StringSupport.trimOrNull(newDigestMethod);
+    }
+
+    /**
+     * Get the Base64-encoded nonce value.
+     * 
+     * @return the nonce value
+     */
+    @Nullable public String getNonce() {
+        return nonce;
+    }
+
+    /**
+     * Set the digest method algorithm URI.
+     * 
+     * @param newNonce the algorithm URI
+     */
+    public void setNonce(@Nullable final String newNonce) {
+        nonce = StringSupport.trimOrNull(newNonce);
+    }
+
+    /** {@inheritDoc} */
+    public SecretKey derive(@Nonnull final byte[] secret, @Nonnull final String keyAlgorithm,
+            @Nullable final Integer keyLength) throws KeyDerivationException {
+        Constraint.isNotNull(secret, "Secret byte[] was null");
+        Constraint.isNotNull(keyAlgorithm, "Key algorithm was null");
+        
+        final String jcaKeyAlgorithm = KeyDerivationSupport.getJCAKeyAlgorithm(keyAlgorithm);
+        
+        final Integer jcaKeyLength = KeyDerivationSupport.getEffectiveKeyLength(keyAlgorithm, keyLength);
+        
+        final byte[] keyBytes = deriveBytes(secret, keyAlgorithm, jcaKeyLength);
+        
+        return new SecretKeySpec(keyBytes, jcaKeyAlgorithm); 
+    }
+
+    /**
+     * Derive the key bytes from the specified inputs.
+     * 
+     * @param secret the input secret
+     * @param encryptionAlgorithm the encryption algorithm URI to be used with the derived key
+     * @param keyLength the key length
+     * 
+     * @return derived bytes the derived key bytes
+     * 
+     * @throws KeyDerivationException if any of the inputs are invalid
+     */
+    protected byte[] deriveBytes(@Nonnull final byte[] secret, @Nonnull final String encryptionAlgorithm,
+            @Nonnull final Integer keyLength) throws KeyDerivationException {
+        
+        byte[] derived = new byte[] {};
+        
+        final String jcaDigest = AlgorithmSupport.getAlgorithmID(digestMethod);
+        if (jcaDigest == null) {
+            log.warn("Could not resolve JCA algorithm ID from URI: {}", jcaDigest);
+            throw new KeyDerivationException("Could not resolve JCA digest from URI: " + digestMethod);
+        }
+        
+        try {
+            final byte[] nonceBytes = nonce != null ? Base64Support.decode(nonce) : new byte[] {};
+            int counter = 0;
+            while ((derived.length * 8) < keyLength) {
+                derived = Bytes.concat(derived, digest(++counter, jcaDigest, secret, encryptionAlgorithm, keyLength,
+                        nonceBytes));
+            }
+        } catch (final DecodingException e) { 
+            log.error("Fatal error Base64-decoding supplied nonce value: {}", nonce, e);
+            throw new KeyDerivationException("Fatal error decoding nonce", e);
+        }
+            
+        return Arrays.copyOfRange(derived, 0, keyLength/8);
+    }
+
+    /**
+     * Produce the digest of the specified inputs according to XML Encryption section 1.1, section 5.6.2.2.
+     * 
+     * @param counter the counter value
+     * @param digestAlgorithm the JCA digest algorithm
+     * @param secret the input secret
+     * @param encryptionAlgorithm the encryption algorithm URI to be used with the derived key
+     * @param keyLength the key length
+     * @param nonceBytes the nonce, which may be an empty byte[] array, but not null
+     * 
+     * @return digest output for the specified inputs
+     * 
+     * @throws KeyDerivationException if any of the inputs are invalid
+     */
+    // CheckStyle: ParameterNumber OFF
+    protected byte[] digest(final int counter, @Nonnull final String digestAlgorithm, @Nonnull final byte[] secret,
+            @Nonnull final String encryptionAlgorithm, @Nonnull final Integer keyLength,
+            @Nonnull final byte[] nonceBytes) throws KeyDerivationException {
+        
+        final byte[] digestInput = Bytes.concat(
+                secret,
+                String.format("%02d", counter).getBytes(Charsets.UTF_8),
+                encryptionAlgorithm.getBytes(Charsets.UTF_8),
+                nonceBytes,
+                keyLength.toString().getBytes(Charsets.UTF_8));
+        
+        log.trace("Digest input for counter={} in hex was: {}", counter, Hex.encodeHexString(digestInput, false));
+        
+        try {
+            final MessageDigest md = MessageDigest.getInstance(digestAlgorithm);
+            final byte[] output = md.digest(digestInput);
+            log.trace("Digest output for counter={} in hex was: {}", counter, Hex.encodeHexString(output, false));
+            return output;
+        } catch (final NoSuchAlgorithmException e) {
+            throw new KeyDerivationException("Fatal error computing digest for key derivation", e);
+        }
+    }
+    // CheckStyle: ParameterNumber ON
+    
+}
diff --git a/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/signature/impl/KeyValueImpl.java b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/signature/impl/KeyValueImpl.java
index d1186209c..fb798e8fc 100644
--- a/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/signature/impl/KeyValueImpl.java
+++ b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/signature/impl/KeyValueImpl.java
@@ -23,6 +23,7 @@ import java.util.List;
 
 import org.opensaml.core.xml.AbstractXMLObject;
 import org.opensaml.core.xml.XMLObject;
+import org.opensaml.xmlsec.encryption.DHKeyValue;
 import org.opensaml.xmlsec.signature.DSAKeyValue;
 import org.opensaml.xmlsec.signature.ECKeyValue;
 import org.opensaml.xmlsec.signature.KeyValue;
@@ -32,6 +33,9 @@ import org.opensaml.xmlsec.signature.RSAKeyValue;
  * Concrete implementation of {@link org.opensaml.xmlsec.signature.KeyValue}.
  */
 public class KeyValueImpl extends AbstractXMLObject implements KeyValue {
+ 
+    /** DHKeyValue child element. */
+    private DHKeyValue dhKeyValue;
     
     /** DSAKeyValue child element. */
     private DSAKeyValue dsaKeyValue;
@@ -56,6 +60,16 @@ public class KeyValueImpl extends AbstractXMLObject implements KeyValue {
         super(namespaceURI, elementLocalName, namespacePrefix);
     }
 
+    /** {@inheritDoc} */
+    public DHKeyValue getDHKeyValue() {
+        return dhKeyValue;
+    }
+
+    /** {@inheritDoc} */
+    public void setDHKeyValue(final DHKeyValue newDHKeyValue) {
+        dhKeyValue = prepareForAssignment(dhKeyValue, newDHKeyValue);
+    }
+
     /** {@inheritDoc} */
     public DSAKeyValue getDSAKeyValue() {
         return dsaKeyValue;
@@ -109,6 +123,9 @@ public class KeyValueImpl extends AbstractXMLObject implements KeyValue {
         if (ecKeyValue != null) {
             children.add(ecKeyValue);
         }
+        if (dhKeyValue != null) {
+            children.add(dhKeyValue);
+        }
         if (unknownXMLObject != null) {
             children.add(unknownXMLObject);
         }
diff --git a/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/signature/impl/KeyValueUnmarshaller.java b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/signature/impl/KeyValueUnmarshaller.java
index 8e3aa8940..d1d76fc22 100644
--- a/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/signature/impl/KeyValueUnmarshaller.java
+++ b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/signature/impl/KeyValueUnmarshaller.java
@@ -19,6 +19,7 @@ package org.opensaml.xmlsec.signature.impl;
 
 import org.opensaml.core.xml.XMLObject;
 import org.opensaml.core.xml.io.UnmarshallingException;
+import org.opensaml.xmlsec.encryption.DHKeyValue;
 import org.opensaml.xmlsec.signature.DSAKeyValue;
 import org.opensaml.xmlsec.signature.ECKeyValue;
 import org.opensaml.xmlsec.signature.KeyValue;
@@ -40,6 +41,8 @@ public class KeyValueUnmarshaller extends AbstractXMLSignatureUnmarshaller {
             keyValue.setRSAKeyValue((RSAKeyValue) childXMLObject);
         } else if (childXMLObject instanceof ECKeyValue) {
             keyValue.setECKeyValue((ECKeyValue) childXMLObject);
+        } else if (childXMLObject instanceof DHKeyValue) {
+            keyValue.setDHKeyValue((DHKeyValue) childXMLObject);
         } else {
             // There can be only one...
             if (keyValue.getUnknownXMLObject() == null) {
diff --git a/opensaml-xmlsec-impl/src/main/resources/META-INF/services/org.opensaml.xmlsec.agreement.KeyAgreementProcessor b/opensaml-xmlsec-impl/src/main/resources/META-INF/services/org.opensaml.xmlsec.agreement.KeyAgreementProcessor
index b5d636f8b..ff767caf6 100644
--- a/opensaml-xmlsec-impl/src/main/resources/META-INF/services/org.opensaml.xmlsec.agreement.KeyAgreementProcessor
+++ b/opensaml-xmlsec-impl/src/main/resources/META-INF/services/org.opensaml.xmlsec.agreement.KeyAgreementProcessor
@@ -1 +1,3 @@
-org.opensaml.xmlsec.agreement.impl.ECDHKeyAgreementProcessor
\ No newline at end of file
+org.opensaml.xmlsec.agreement.impl.ECDHKeyAgreementProcessor
+org.opensaml.xmlsec.agreement.impl.DHWithExplicitKDFKeyAgreementProcessor
+org.opensaml.xmlsec.agreement.impl.DHWithLegacyKDFKeyAgreementProcessor
\ No newline at end of file
diff --git a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/agreement/impl/ECDHKeyAgreementProcessorTest.java b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/agreement/impl/DHWithExplicitKDFKeyAgreementProcessorTest.java
similarity index 88%
copy from opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/agreement/impl/ECDHKeyAgreementProcessorTest.java
copy to opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/agreement/impl/DHWithExplicitKDFKeyAgreementProcessorTest.java
index 0a909e659..9e0ed3648 100644
--- a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/agreement/impl/ECDHKeyAgreementProcessorTest.java
+++ b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/agreement/impl/DHWithExplicitKDFKeyAgreementProcessorTest.java
@@ -18,7 +18,6 @@
 package org.opensaml.xmlsec.agreement.impl;
 
 import java.security.KeyPair;
-import java.security.spec.ECGenParameterSpec;
 import java.util.ArrayList;
 import java.util.Collection;
 
@@ -40,18 +39,18 @@ import org.testng.annotations.Test;
 /**
  *
  */
-public class ECDHKeyAgreementProcessorTest extends OpenSAMLInitBaseTestCase {
+public class DHWithExplicitKDFKeyAgreementProcessorTest extends OpenSAMLInitBaseTestCase {
     
-    private ECDHKeyAgreementProcessor processor;
+    private DHWithExplicitKDFKeyAgreementProcessor processor;
     
     @BeforeMethod
     public void setUp() {
-        processor = new ECDHKeyAgreementProcessor();
+        processor = new DHWithExplicitKDFKeyAgreementProcessor();
     }
     
     @Test
     public void encryptingCase() throws Exception {
-        KeyPair recipientKeyPair = KeySupport.generateKeyPair("EC", new ECGenParameterSpec("secp256r1"), null);
+        KeyPair recipientKeyPair = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN, 2048, null);
         Credential recipientCredential = CredentialSupport.getSimpleCredential(recipientKeyPair.getPublic(), null);
         
         KeyAgreementParameters params = new KeyAgreementParameters();
@@ -81,21 +80,21 @@ public class ECDHKeyAgreementProcessorTest extends OpenSAMLInitBaseTestCase {
         Assert.assertNotNull(keyAgreementCredential.getOriginatorCredential().getPrivateKey());
         Assert.assertNull(keyAgreementCredential.getOriginatorCredential().getSecretKey());
         
-        Assert.assertEquals(keyAgreementCredential.getAlgorithm(), EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES);
+        Assert.assertEquals(keyAgreementCredential.getAlgorithm(), EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH_EXPLICIT_KDF);
         
         Assert.assertEquals(keyAgreementCredential.getParameters().size(), 2);
         Assert.assertTrue(keyAgreementCredential.getParameters().contains(MockKeyDerivation.class));
         Assert.assertTrue(keyAgreementCredential.getParameters().contains(KANonce.class));
-        Assert.assertEquals(keyAgreementCredential.getParameters().get(KANonce.class).getValue(), "someBase64");
+        Assert.assertEquals(keyAgreementCredential.getParameters().get(KANonce.class).getValue(), "AABBCCDD");
         
     }
     
     @Test
     public void decryptingCase() throws Exception {
-        KeyPair originatorKeyPair = KeySupport.generateKeyPair("EC", new ECGenParameterSpec("secp256r1"), null);
+        KeyPair originatorKeyPair = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN, 2048, null);
         Credential originatorCredential = CredentialSupport.getSimpleCredential(originatorKeyPair.getPublic(), null);
         
-        KeyPair recipientKeyPair = KeySupport.generateKeyPair("EC", new ECGenParameterSpec("secp256r1"), null);
+        KeyPair recipientKeyPair = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN, 2048, null);
         Credential recipientCredential = CredentialSupport.getSimpleCredential(recipientKeyPair.getPublic(), recipientKeyPair.getPrivate());
         
         KeyAgreementParameters params = new KeyAgreementParameters();
@@ -126,13 +125,13 @@ public class ECDHKeyAgreementProcessorTest extends OpenSAMLInitBaseTestCase {
         Assert.assertNull(keyAgreementCredential.getOriginatorCredential().getPrivateKey());
         Assert.assertNull(keyAgreementCredential.getOriginatorCredential().getSecretKey());
         
-        Assert.assertEquals(keyAgreementCredential.getAlgorithm(), EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES);
+        Assert.assertEquals(keyAgreementCredential.getAlgorithm(), EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH_EXPLICIT_KDF);
         
         Assert.assertEquals(keyAgreementCredential.getParameters().size(), 3);
         Assert.assertTrue(keyAgreementCredential.getParameters().contains(PrivateCredential.class));
         Assert.assertTrue(keyAgreementCredential.getParameters().contains(MockKeyDerivation.class));
         Assert.assertTrue(keyAgreementCredential.getParameters().contains(KANonce.class));
-        Assert.assertEquals(keyAgreementCredential.getParameters().get(KANonce.class).getValue(), "someBase64");
+        Assert.assertEquals(keyAgreementCredential.getParameters().get(KANonce.class).getValue(), "AABBCCDD");
         
     }
 
@@ -152,7 +151,7 @@ public class ECDHKeyAgreementProcessorTest extends OpenSAMLInitBaseTestCase {
     
     @Test(expectedExceptions = KeyAgreementException.class)
     public void keyDerivationError() throws Exception {
-        KeyPair kp = KeySupport.generateKeyPair("EC", new ECGenParameterSpec("secp256r1"), null);
+        KeyPair kp = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN, 2048, null);
         Credential publicCredential = CredentialSupport.getSimpleCredential(kp.getPublic(), null);
         
         KeyAgreementParameters params = new KeyAgreementParameters();
@@ -166,7 +165,7 @@ public class ECDHKeyAgreementProcessorTest extends OpenSAMLInitBaseTestCase {
     
     @Test(expectedExceptions = KeyAgreementException.class)
     public void missingKeyDerivationParam() throws Exception {
-        KeyPair kp = KeySupport.generateKeyPair("EC", new ECGenParameterSpec("secp256r1"), null);
+        KeyPair kp = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN, 2048, null);
         Credential publicCredential = CredentialSupport.getSimpleCredential(kp.getPublic(), null);
         
         KeyAgreementParameters params = new KeyAgreementParameters();
@@ -179,7 +178,7 @@ public class ECDHKeyAgreementProcessorTest extends OpenSAMLInitBaseTestCase {
     
     @Test(expectedExceptions = KeyAgreementException.class)
     public void specifiedKeySizeMismatch() throws Exception {
-        KeyPair kp = KeySupport.generateKeyPair("EC", new ECGenParameterSpec("secp256r1"), null);
+        KeyPair kp = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN, 2048, null);
         Credential publicCredential = CredentialSupport.getSimpleCredential(kp.getPublic(), null);
         
         KeyAgreementParameters params = new KeyAgreementParameters();
@@ -194,7 +193,7 @@ public class ECDHKeyAgreementProcessorTest extends OpenSAMLInitBaseTestCase {
     private Collection<KeyAgreementParameter> getMockParams() {
         ArrayList<KeyAgreementParameter> params = new ArrayList<>();
         KANonce nonce = new KANonce();
-        nonce.setValue("someBase64");
+        nonce.setValue("AABBCCDD");
         params.add(nonce);
         return params;
     }
diff --git a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/agreement/impl/ECDHKeyAgreementProcessorTest.java b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/agreement/impl/DHWithLegacyKDFKeyAgreementProcessorTest.java
similarity index 73%
copy from opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/agreement/impl/ECDHKeyAgreementProcessorTest.java
copy to opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/agreement/impl/DHWithLegacyKDFKeyAgreementProcessorTest.java
index 0a909e659..6dd82b3c7 100644
--- a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/agreement/impl/ECDHKeyAgreementProcessorTest.java
+++ b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/agreement/impl/DHWithLegacyKDFKeyAgreementProcessorTest.java
@@ -18,7 +18,6 @@
 package org.opensaml.xmlsec.agreement.impl;
 
 import java.security.KeyPair;
-import java.security.spec.ECGenParameterSpec;
 import java.util.ArrayList;
 import java.util.Collection;
 
@@ -33,6 +32,7 @@ import org.opensaml.xmlsec.agreement.KeyAgreementParameter;
 import org.opensaml.xmlsec.agreement.KeyAgreementParameters;
 import org.opensaml.xmlsec.derivation.impl.MockKeyDerivation;
 import org.opensaml.xmlsec.encryption.support.EncryptionConstants;
+import org.opensaml.xmlsec.signature.support.SignatureConstants;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -40,23 +40,29 @@ import org.testng.annotations.Test;
 /**
  *
  */
-public class ECDHKeyAgreementProcessorTest extends OpenSAMLInitBaseTestCase {
+public class DHWithLegacyKDFKeyAgreementProcessorTest extends OpenSAMLInitBaseTestCase {
     
-    private ECDHKeyAgreementProcessor processor;
+    private DHWithLegacyKDFKeyAgreementProcessor processor;
     
     @BeforeMethod
     public void setUp() {
-        processor = new ECDHKeyAgreementProcessor();
+        processor = new DHWithLegacyKDFKeyAgreementProcessor();
     }
     
     @Test
     public void encryptingCase() throws Exception {
-        KeyPair recipientKeyPair = KeySupport.generateKeyPair("EC", new ECGenParameterSpec("secp256r1"), null);
+        KeyPair recipientKeyPair = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN, 2048, null);
         Credential recipientCredential = CredentialSupport.getSimpleCredential(recipientKeyPair.getPublic(), null);
         
         KeyAgreementParameters params = new KeyAgreementParameters();
-        params.add(new MockKeyDerivation()); 
-        params.addAll(getMockParams());
+        DigestMethod dm = new DigestMethod();
+        dm.setAlgorithm(SignatureConstants.ALGO_ID_DIGEST_SHA256);
+        dm.initialize();
+        params.add(dm);
+        KANonce nonce = new KANonce();
+        nonce.setValue("AABBCCDD");
+        nonce.initialize();
+        params.add(nonce);
         
         KeyAgreementCredential keyAgreementCredential = processor.execute(recipientCredential,
                 EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128,
@@ -81,27 +87,33 @@ public class ECDHKeyAgreementProcessorTest extends OpenSAMLInitBaseTestCase {
         Assert.assertNotNull(keyAgreementCredential.getOriginatorCredential().getPrivateKey());
         Assert.assertNull(keyAgreementCredential.getOriginatorCredential().getSecretKey());
         
-        Assert.assertEquals(keyAgreementCredential.getAlgorithm(), EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES);
+        Assert.assertEquals(keyAgreementCredential.getAlgorithm(), EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH);
         
         Assert.assertEquals(keyAgreementCredential.getParameters().size(), 2);
-        Assert.assertTrue(keyAgreementCredential.getParameters().contains(MockKeyDerivation.class));
+        Assert.assertTrue(keyAgreementCredential.getParameters().contains(DigestMethod.class));
+        Assert.assertEquals(keyAgreementCredential.getParameters().get(DigestMethod.class).getAlgorithm(), SignatureConstants.ALGO_ID_DIGEST_SHA256);
         Assert.assertTrue(keyAgreementCredential.getParameters().contains(KANonce.class));
-        Assert.assertEquals(keyAgreementCredential.getParameters().get(KANonce.class).getValue(), "someBase64");
-        
+        Assert.assertEquals(keyAgreementCredential.getParameters().get(KANonce.class).getValue(), "AABBCCDD");
     }
     
     @Test
     public void decryptingCase() throws Exception {
-        KeyPair originatorKeyPair = KeySupport.generateKeyPair("EC", new ECGenParameterSpec("secp256r1"), null);
+        KeyPair originatorKeyPair = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN, 2048, null);
         Credential originatorCredential = CredentialSupport.getSimpleCredential(originatorKeyPair.getPublic(), null);
         
-        KeyPair recipientKeyPair = KeySupport.generateKeyPair("EC", new ECGenParameterSpec("secp256r1"), null);
+        KeyPair recipientKeyPair = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN, 2048, null);
         Credential recipientCredential = CredentialSupport.getSimpleCredential(recipientKeyPair.getPublic(), recipientKeyPair.getPrivate());
         
         KeyAgreementParameters params = new KeyAgreementParameters();
         params.add(new PrivateCredential(recipientCredential));
-        params.add(new MockKeyDerivation()); 
-        params.addAll(getMockParams());
+        DigestMethod dm = new DigestMethod();
+        dm.setAlgorithm(SignatureConstants.ALGO_ID_DIGEST_SHA256);
+        dm.initialize();
+        params.add(dm);
+        KANonce nonce = new KANonce();
+        nonce.setValue("AABBCCDD");
+        nonce.initialize();
+        params.add(nonce);
         
         KeyAgreementCredential keyAgreementCredential = processor.execute(originatorCredential,
                 EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128,
@@ -126,13 +138,14 @@ public class ECDHKeyAgreementProcessorTest extends OpenSAMLInitBaseTestCase {
         Assert.assertNull(keyAgreementCredential.getOriginatorCredential().getPrivateKey());
         Assert.assertNull(keyAgreementCredential.getOriginatorCredential().getSecretKey());
         
-        Assert.assertEquals(keyAgreementCredential.getAlgorithm(), EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES);
+        Assert.assertEquals(keyAgreementCredential.getAlgorithm(), EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH);
         
         Assert.assertEquals(keyAgreementCredential.getParameters().size(), 3);
         Assert.assertTrue(keyAgreementCredential.getParameters().contains(PrivateCredential.class));
-        Assert.assertTrue(keyAgreementCredential.getParameters().contains(MockKeyDerivation.class));
+        Assert.assertTrue(keyAgreementCredential.getParameters().contains(DigestMethod.class));
+        Assert.assertEquals(keyAgreementCredential.getParameters().get(DigestMethod.class).getAlgorithm(), SignatureConstants.ALGO_ID_DIGEST_SHA256);
         Assert.assertTrue(keyAgreementCredential.getParameters().contains(KANonce.class));
-        Assert.assertEquals(keyAgreementCredential.getParameters().get(KANonce.class).getValue(), "someBase64");
+        Assert.assertEquals(keyAgreementCredential.getParameters().get(KANonce.class).getValue(), "AABBCCDD");
         
     }
 
@@ -142,8 +155,14 @@ public class ECDHKeyAgreementProcessorTest extends OpenSAMLInitBaseTestCase {
         Credential publicCredential = CredentialSupport.getSimpleCredential(kp.getPublic(), null);
         
         KeyAgreementParameters params = new KeyAgreementParameters();
-        params.add(new MockKeyDerivation()); 
-        params.addAll(getMockParams());
+        DigestMethod dm = new DigestMethod();
+        dm.setAlgorithm(SignatureConstants.ALGO_ID_DIGEST_SHA256);
+        dm.initialize();
+        params.add(dm);
+        KANonce nonce = new KANonce();
+        nonce.setValue("AABBCCDD");
+        nonce.initialize();
+        params.add(nonce);
         
         processor.execute(publicCredential,
                 EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128,
@@ -151,53 +170,45 @@ public class ECDHKeyAgreementProcessorTest extends OpenSAMLInitBaseTestCase {
     }
     
     @Test(expectedExceptions = KeyAgreementException.class)
-    public void keyDerivationError() throws Exception {
-        KeyPair kp = KeySupport.generateKeyPair("EC", new ECGenParameterSpec("secp256r1"), null);
+    public void invalidKeyAlgorithm() throws Exception {
+        KeyPair kp = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN, 2048, null);
         Credential publicCredential = CredentialSupport.getSimpleCredential(kp.getPublic(), null);
         
         KeyAgreementParameters params = new KeyAgreementParameters();
-        params.add(new MockKeyDerivation()); 
-        params.addAll(getMockParams());
+        DigestMethod dm = new DigestMethod();
+        dm.setAlgorithm(SignatureConstants.ALGO_ID_DIGEST_SHA256);
+        dm.initialize();
+        params.add(dm);
+        KANonce nonce = new KANonce();
+        nonce.setValue("AABBCCDD");
+        nonce.initialize();
+        params.add(nonce);
         
         processor.execute(publicCredential,
                 "urn:test:InvalidBlockEncryption",
                 params);
     }
     
-    @Test(expectedExceptions = KeyAgreementException.class)
-    public void missingKeyDerivationParam() throws Exception {
-        KeyPair kp = KeySupport.generateKeyPair("EC", new ECGenParameterSpec("secp256r1"), null);
-        Credential publicCredential = CredentialSupport.getSimpleCredential(kp.getPublic(), null);
-        
-        KeyAgreementParameters params = new KeyAgreementParameters();
-        params.addAll(getMockParams());
-        
-        processor.execute(publicCredential,
-                EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128,
-                params);
-    }
-    
     @Test(expectedExceptions = KeyAgreementException.class)
     public void specifiedKeySizeMismatch() throws Exception {
-        KeyPair kp = KeySupport.generateKeyPair("EC", new ECGenParameterSpec("secp256r1"), null);
+        KeyPair kp = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN, 2048, null);
         Credential publicCredential = CredentialSupport.getSimpleCredential(kp.getPublic(), null);
         
         KeyAgreementParameters params = new KeyAgreementParameters();
-        params.add(new MockKeyDerivation()); 
         params.add(new KeySize(256));
+        DigestMethod dm = new DigestMethod();
+        dm.setAlgorithm(SignatureConstants.ALGO_ID_DIGEST_SHA256);
+        dm.initialize();
+        params.add(dm);
+        KANonce nonce = new KANonce();
+        nonce.setValue("AABBCCDD");
+        nonce.initialize();
+        params.add(nonce);
+        
         
         processor.execute(publicCredential,
                 EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128,
                 params);
     }
-    
-    private Collection<KeyAgreementParameter> getMockParams() {
-        ArrayList<KeyAgreementParameter> params = new ArrayList<>();
-        KANonce nonce = new KANonce();
-        nonce.setValue("someBase64");
-        params.add(nonce);
-        return params;
-    }
-    
         
 }
diff --git a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/agreement/impl/ECDHKeyAgreementProcessorTest.java b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/agreement/impl/ECDHKeyAgreementProcessorTest.java
index 0a909e659..af74f296e 100644
--- a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/agreement/impl/ECDHKeyAgreementProcessorTest.java
+++ b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/agreement/impl/ECDHKeyAgreementProcessorTest.java
@@ -86,7 +86,7 @@ public class ECDHKeyAgreementProcessorTest extends OpenSAMLInitBaseTestCase {
         Assert.assertEquals(keyAgreementCredential.getParameters().size(), 2);
         Assert.assertTrue(keyAgreementCredential.getParameters().contains(MockKeyDerivation.class));
         Assert.assertTrue(keyAgreementCredential.getParameters().contains(KANonce.class));
-        Assert.assertEquals(keyAgreementCredential.getParameters().get(KANonce.class).getValue(), "someBase64");
+        Assert.assertEquals(keyAgreementCredential.getParameters().get(KANonce.class).getValue(), "AABBCCDD");
         
     }
     
@@ -132,7 +132,7 @@ public class ECDHKeyAgreementProcessorTest extends OpenSAMLInitBaseTestCase {
         Assert.assertTrue(keyAgreementCredential.getParameters().contains(PrivateCredential.class));
         Assert.assertTrue(keyAgreementCredential.getParameters().contains(MockKeyDerivation.class));
         Assert.assertTrue(keyAgreementCredential.getParameters().contains(KANonce.class));
-        Assert.assertEquals(keyAgreementCredential.getParameters().get(KANonce.class).getValue(), "someBase64");
+        Assert.assertEquals(keyAgreementCredential.getParameters().get(KANonce.class).getValue(), "AABBCCDD");
         
     }
 
@@ -194,7 +194,7 @@ public class ECDHKeyAgreementProcessorTest extends OpenSAMLInitBaseTestCase {
     private Collection<KeyAgreementParameter> getMockParams() {
         ArrayList<KeyAgreementParameter> params = new ArrayList<>();
         KANonce nonce = new KANonce();
-        nonce.setValue("someBase64");
+        nonce.setValue("AABBCCDD");
         params.add(nonce);
         return params;
     }
diff --git a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/agreement/impl/KANonceTest.java b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/agreement/impl/KANonceTest.java
index fb5989803..64520465d 100644
--- a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/agreement/impl/KANonceTest.java
+++ b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/agreement/impl/KANonceTest.java
@@ -22,6 +22,7 @@ import org.opensaml.core.xml.XMLObject;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import net.shibboleth.utilities.java.support.codec.Base64Support;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.UnmodifiableComponentException;
 
@@ -31,14 +32,14 @@ import net.shibboleth.utilities.java.support.component.UnmodifiableComponentExce
 public class KANonceTest extends XMLObjectBaseTestCase  {
     
     @Test
-    public void basic() throws ComponentInitializationException {
+    public void basic() throws Exception {
         KANonce nonce = new KANonce();
-        nonce.setValue("someBase64==   ");
+        nonce.setValue("AABBCCDD");
         nonce.initialize();
-        Assert.assertEquals(nonce.getValue(), "someBase64==");
+        Assert.assertEquals(nonce.getValue(), "AABBCCDD");
         
         try {
-            nonce.setValue("foo");
+            nonce.setValue("BBCCDDEE");
             Assert.fail("Modify of initialzied component should have failed");
         } catch (UnmodifiableComponentException e) {
             // expected
@@ -46,25 +47,51 @@ public class KANonceTest extends XMLObjectBaseTestCase  {
         
         KANonce cloned  = nonce.clone();
         Assert.assertTrue(cloned.isInitialized());
-        Assert.assertEquals(cloned.getValue(), "someBase64==");
+        Assert.assertEquals(cloned.getValue(), "AABBCCDD");
         
         XMLObject xmlObject = nonce.buildXMLObject();
         Assert.assertNotNull(xmlObject);
         Assert.assertTrue(org.opensaml.xmlsec.encryption.KANonce.class.isInstance(xmlObject));
         org.opensaml.xmlsec.encryption.KANonce xmlNonce = org.opensaml.xmlsec.encryption.KANonce.class.cast(xmlObject);
-        Assert.assertEquals(xmlNonce.getValue(), "someBase64==");
+        Assert.assertEquals(xmlNonce.getValue(), "AABBCCDD");
         
     }
     
+    @Test()
+    public void generatedValue() throws Exception {
+        KANonce nonce = new KANonce();
+        // Don't generate a value unless initialized
+        Assert.assertNull(nonce.getValue());
+        nonce.initialize();
+        
+        String initValue = nonce.getValue();
+        Assert.assertNotNull(initValue);
+        Assert.assertEquals(Base64Support.decode(initValue).length, nonce.getGeneratedLength().intValue());
+        // Once generated value shouldn't change
+        Assert.assertEquals(nonce.getValue(), initValue);
+        Assert.assertEquals(nonce.getValue(), initValue);
+    }
+    
+    @Test()
+    public void generatedLength() throws Exception {
+        KANonce nonce = new KANonce();
+        nonce.setGeneratedLength(16);
+        nonce.initialize();
+        
+        String initValue = nonce.getValue();
+        Assert.assertNotNull(initValue);
+        Assert.assertEquals(Base64Support.decode(initValue).length, nonce.getGeneratedLength().intValue());
+    }
+    
     @Test
     public void fromXMLObject() throws Exception {
         org.opensaml.xmlsec.encryption.KANonce xmlObject = buildXMLObject(org.opensaml.xmlsec.encryption.KANonce.DEFAULT_ELEMENT_NAME);
-        xmlObject.setValue("someBase64==");
+        xmlObject.setValue("AABBCCDD");
         
         KANonce parameter = KANonce.fromXMLObject(xmlObject);
         Assert.assertNotNull(parameter);
         Assert.assertTrue(parameter.isInitialized());
-        Assert.assertEquals(parameter.getValue(), "someBase64==");
+        Assert.assertEquals(parameter.getValue(), "AABBCCDD");
         
         xmlObject.setValue(null);
         
@@ -77,10 +104,4 @@ public class KANonceTest extends XMLObjectBaseTestCase  {
         }
     }
     
-    @Test(expectedExceptions = ComponentInitializationException.class)
-    public void missingValue() throws ComponentInitializationException {
-        KANonce nonce = new KANonce();
-        nonce.initialize();
-    }
-    
 }
diff --git a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/config/impl/GlobalKeyAgreementProcessorRegistryTest.java b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/config/impl/GlobalKeyAgreementProcessorRegistryTest.java
index 337e649f1..aba9cbf3a 100644
--- a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/config/impl/GlobalKeyAgreementProcessorRegistryTest.java
+++ b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/config/impl/GlobalKeyAgreementProcessorRegistryTest.java
@@ -22,6 +22,8 @@ import java.util.Set;
 import org.opensaml.core.testing.OpenSAMLInitBaseTestCase;
 import org.opensaml.xmlsec.agreement.KeyAgreementProcessorRegistry;
 import org.opensaml.xmlsec.agreement.KeyAgreementSupport;
+import org.opensaml.xmlsec.agreement.impl.DHWithExplicitKDFKeyAgreementProcessor;
+import org.opensaml.xmlsec.agreement.impl.DHWithLegacyKDFKeyAgreementProcessor;
 import org.opensaml.xmlsec.agreement.impl.ECDHKeyAgreementProcessor;
 import org.opensaml.xmlsec.encryption.support.EncryptionConstants;
 import org.testng.Assert;
@@ -37,12 +39,22 @@ public class GlobalKeyAgreementProcessorRegistryTest extends OpenSAMLInitBaseTes
        KeyAgreementProcessorRegistry registry = KeyAgreementSupport.getGlobalProcessorRegistry(); 
        
        Assert.assertNotNull(registry);
-       Assert.assertEquals(registry.getRegisteredAlgorithms().size(), 1);
+       Assert.assertEquals(registry.getRegisteredAlgorithms().size(), 3);
+       
+       Assert.assertEquals(registry.getRegisteredAlgorithms(), Set.of(EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES,
+               EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH, EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH_EXPLICIT_KDF));
        
-       Assert.assertEquals(registry.getRegisteredAlgorithms(), Set.of(EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES));
        Assert.assertNotNull(registry.getProcessor(EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES));
        Assert.assertTrue(ECDHKeyAgreementProcessor.class.isInstance(
                registry.getProcessor(EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES)));
+       
+       Assert.assertNotNull(registry.getProcessor(EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH));
+       Assert.assertTrue(DHWithLegacyKDFKeyAgreementProcessor.class.isInstance(
+               registry.getProcessor(EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH)));
+       
+       Assert.assertNotNull(registry.getProcessor(EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH_EXPLICIT_KDF));
+       Assert.assertTrue(DHWithExplicitKDFKeyAgreementProcessor.class.isInstance(
+               registry.getProcessor(EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH_EXPLICIT_KDF)));
     }
 
 }
diff --git a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/derivation/impl/ConcatKDFTest.java b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/derivation/impl/ConcatKDFTest.java
index 85561c287..d2ef571ee 100644
--- a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/derivation/impl/ConcatKDFTest.java
+++ b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/derivation/impl/ConcatKDFTest.java
@@ -21,11 +21,9 @@ import javax.crypto.SecretKey;
 
 import org.apache.commons.codec.DecoderException;
 import org.apache.commons.codec.binary.Hex;
-import org.opensaml.core.testing.OpenSAMLInitBaseTestCase;
 import org.opensaml.core.testing.XMLObjectBaseTestCase;
 import org.opensaml.core.xml.XMLObject;
 import org.opensaml.core.xml.util.XMLObjectSupport;
-import org.opensaml.xmlsec.agreement.impl.KANonce;
 import org.opensaml.xmlsec.derivation.KeyDerivationException;
 import org.opensaml.xmlsec.encryption.ConcatKDFParams;
 import org.opensaml.xmlsec.encryption.KeyDerivationMethod;
diff --git a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/derivation/impl/DHLegacyKDFTest.java b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/derivation/impl/DHLegacyKDFTest.java
new file mode 100644
index 000000000..3e5e15ebe
--- /dev/null
+++ b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/derivation/impl/DHLegacyKDFTest.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opensaml.xmlsec.derivation.impl;
+
+import javax.crypto.SecretKey;
+
+import org.apache.commons.codec.binary.Hex;
+import org.opensaml.core.testing.OpenSAMLInitBaseTestCase;
+import org.opensaml.security.crypto.JCAConstants;
+import org.opensaml.xmlsec.derivation.KeyDerivationException;
+import org.opensaml.xmlsec.encryption.support.EncryptionConstants;
+import org.opensaml.xmlsec.signature.support.SignatureConstants;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import net.shibboleth.utilities.java.support.codec.Base64Support;
+
+/**
+ *
+ */
+public class DHLegacyKDFTest extends OpenSAMLInitBaseTestCase {
+    
+    @Test
+    public void specTestVector() throws Exception {
+        // This tests the example test data from XML Encryption 1.1 section 5.6.2.2.
+        DHLegacyKDF kdf = new DHLegacyKDF(); 
+        kdf.setDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA1);
+        kdf.setNonce("Zm9v");
+        
+        byte[] digestCounter1 = kdf.digest(
+                1,
+                JCAConstants.DIGEST_SHA1,
+                Hex.decodeHex("DEADBEEF"),
+                "Example:Block/Alg",
+                80,
+                Base64Support.decode(kdf.getNonce()));
+        
+        // The value in the original spec document Example 41 is incorrect, as indicated by the errata:
+        // https://www.w3.org/2008/xmlsec/errata/xmlenc-core-11-errata.html
+        // This is the correct value from the errata. (Yes, this wasted a lot of time...).
+        Assert.assertEquals(digestCounter1, Hex.decodeHex("59D9BA5E06072C1194091952B01B8360534AB11E"));
+       
+        byte[] derived = kdf.deriveBytes(
+                Hex.decodeHex("DEADBEEF"),
+                "Example:Block/Alg",
+                80);
+        Assert.assertEquals(derived.length * 8, 80);
+        
+        // Note we can't really test an actual SecretKey derivation b/c 
+        // "Example:Block/Alg" above is not a real algorithm and so can't resolve the JCA ID.
+    }
+    
+    @Test
+    public void basic() throws Exception {
+        DHLegacyKDF kdf = new DHLegacyKDF(); 
+        kdf.setDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256);
+        kdf.setNonce("Zm9v");
+        
+        SecretKey secretKey = null;
+        
+        secretKey = kdf.derive(Hex.decodeHex("DEADBEEF"), EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128, null);
+        Assert.assertNotNull(secretKey);
+        Assert.assertEquals(secretKey.getEncoded().length * 8, 128);
+        
+        secretKey = kdf.derive(Hex.decodeHex("DEADBEEF"), EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128, 128);
+        Assert.assertNotNull(secretKey);
+        Assert.assertEquals(secretKey.getEncoded().length * 8, 128);
+        
+        secretKey = kdf.derive(Hex.decodeHex("DEADBEEF"), EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES192, null);
+        Assert.assertNotNull(secretKey);
+        Assert.assertEquals(secretKey.getEncoded().length * 8, 192);
+        
+        secretKey = kdf.derive(Hex.decodeHex("DEADBEEF"), EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES192, 192);
+        Assert.assertNotNull(secretKey);
+        Assert.assertEquals(secretKey.getEncoded().length * 8, 192);
+        
+        secretKey = kdf.derive(Hex.decodeHex("DEADBEEF"), EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES256, null);
+        Assert.assertNotNull(secretKey);
+        Assert.assertEquals(secretKey.getEncoded().length * 8, 256);
+        
+        secretKey = kdf.derive(Hex.decodeHex("DEADBEEF"), EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES256, 256);
+        Assert.assertNotNull(secretKey);
+        Assert.assertEquals(secretKey.getEncoded().length * 8, 256);
+    }
+
+    @Test
+    public void missingNonce() throws Exception {
+        DHLegacyKDF kdf = new DHLegacyKDF(); 
+        kdf.setDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256);
+        
+        SecretKey secretKey = null;
+        
+        secretKey = kdf.derive(Hex.decodeHex("DEADBEEF"), EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128, null);
+        Assert.assertNotNull(secretKey);
+        Assert.assertEquals(secretKey.getEncoded().length * 8, 128);
+    }
+        
+    @Test(expectedExceptions=KeyDerivationException.class)
+    public void missingDigest() throws Exception {
+        DHLegacyKDF kdf = new DHLegacyKDF(); 
+        kdf.setNonce("Zm9v");
+        
+        kdf.derive(Hex.decodeHex("DEADBEEF"), EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128, null);
+    }
+        
+    @Test(expectedExceptions=KeyDerivationException.class)
+    public void unknownKeyAlgorithm() throws Exception {
+        DHLegacyKDF kdf = new DHLegacyKDF(); 
+        kdf.setDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256);
+        kdf.setNonce("Zm9v");
+        
+        kdf.derive(Hex.decodeHex("DEADBEEF"), "urn:test:invalid", null);
+    }
+        
+    @Test(expectedExceptions=KeyDerivationException.class)
+    public void unknownDigestAlgorithm() throws Exception {
+        DHLegacyKDF kdf = new DHLegacyKDF(); 
+        kdf.setDigestMethod("urn:test:invalid");
+        kdf.setNonce("Zm9v");
+        
+        kdf.derive(Hex.decodeHex("DEADBEEF"), EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128, 128);
+    }
+        
+    @Test(expectedExceptions=KeyDerivationException.class)
+    public void keyLengthMismatch() throws Exception {
+        DHLegacyKDF kdf = new DHLegacyKDF(); 
+        kdf.setDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256);
+        kdf.setNonce("Zm9v");
+        
+        kdf.derive(Hex.decodeHex("DEADBEEF"), EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128, 256);
+    }
+        
+    @Test(expectedExceptions=KeyDerivationException.class)
+    public void invalidNonce() throws Exception {
+        DHLegacyKDF kdf = new DHLegacyKDF(); 
+        kdf.setDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256);
+        kdf.setNonce("INVALID!!!!@@$$##");
+        
+        kdf.derive(Hex.decodeHex("DEADBEEF"), EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128, 128);
+    }
+        
+}
diff --git a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/encryption/support/tests/ECDHTest.java b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/encryption/support/tests/DHWithExplicitKDFTest.java
similarity index 84%
copy from opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/encryption/support/tests/ECDHTest.java
copy to opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/encryption/support/tests/DHWithExplicitKDFTest.java
index f102eeafd..313cd007f 100644
--- a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/encryption/support/tests/ECDHTest.java
+++ b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/encryption/support/tests/DHWithExplicitKDFTest.java
@@ -20,7 +20,6 @@ package org.opensaml.xmlsec.encryption.support.tests;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.security.KeyPair;
-import java.security.spec.ECGenParameterSpec;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -35,6 +34,7 @@ import org.opensaml.core.xml.util.XMLObjectSupport;
 import org.opensaml.security.credential.BasicCredential;
 import org.opensaml.security.credential.Credential;
 import org.opensaml.security.credential.impl.CollectionCredentialResolver;
+import org.opensaml.security.crypto.JCAConstants;
 import org.opensaml.security.crypto.KeySupport;
 import org.opensaml.xmlsec.DecryptionConfiguration;
 import org.opensaml.xmlsec.DecryptionParameters;
@@ -44,7 +44,9 @@ import org.opensaml.xmlsec.EncryptionParameters;
 import org.opensaml.xmlsec.EncryptionParametersResolver;
 import org.opensaml.xmlsec.criterion.DecryptionConfigurationCriterion;
 import org.opensaml.xmlsec.criterion.EncryptionConfigurationCriterion;
+import org.opensaml.xmlsec.derivation.impl.ConcatKDF;
 import org.opensaml.xmlsec.derivation.impl.PBKDF2;
+import org.opensaml.xmlsec.encryption.AgreementMethod;
 import org.opensaml.xmlsec.encryption.EncryptedData;
 import org.opensaml.xmlsec.encryption.KeyDerivationMethod;
 import org.opensaml.xmlsec.encryption.support.DataEncryptionParameters;
@@ -74,7 +76,7 @@ import net.shibboleth.utilities.java.support.xml.SerializeSupport;
 /**
  *
  */
-public class ECDHTest extends XMLObjectBaseTestCase {
+public class DHWithExplicitKDFTest extends XMLObjectBaseTestCase {
     
     private String targetFile;
     
@@ -86,7 +88,7 @@ public class ECDHTest extends XMLObjectBaseTestCase {
     private Encrypter encrypter;
     private EncryptionParametersResolver encParamsResolver;
     private CriteriaSet encCriteria;
-    private BasicEncryptionConfiguration encConfig;
+    private BasicEncryptionConfiguration encConfig, encConfig2;
     
     private DecryptionParametersResolver decryptParamsResolver;
     private CriteriaSet decryptCriteria;
@@ -96,7 +98,7 @@ public class ECDHTest extends XMLObjectBaseTestCase {
     public void beforeClass() throws Exception {
         targetFile = "/org/opensaml/xmlsec/encryption/support/SimpleEncryptionTest.xml";
         
-        KeyPair kp = KeySupport.generateKeyPair("EC", new ECGenParameterSpec("secp256r1"), null);
+        KeyPair kp = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN, 2048, null);
         recipientCredPrivate = new BasicCredential(kp.getPublic(), kp.getPrivate());
         recipientCredPublic = new BasicCredential(kp.getPublic());
         
@@ -115,9 +117,21 @@ public class ECDHTest extends XMLObjectBaseTestCase {
     @BeforeMethod
     public void beforeMethod() throws Exception {
         encConfig = new BasicEncryptionConfiguration();
-        encCriteria = new CriteriaSet(new EncryptionConfigurationCriterion(encConfig,
+        encConfig2 = new BasicEncryptionConfiguration();
+        encCriteria = new CriteriaSet(new EncryptionConfigurationCriterion(encConfig, encConfig2,
                 ConfigurationService.get(EncryptionConfiguration.class)));
         
+        // Configure the middle slot explicitly so that we aren't relying on whichever DH variant the library wide config has.
+        KeyAgreementEncryptionConfiguration kaConfig = new KeyAgreementEncryptionConfiguration();
+        kaConfig.setAlgorithm(EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH_EXPLICIT_KDF);
+        ConcatKDF kdf = new ConcatKDF();
+        kdf.setAlgorithmID("00");
+        kdf.setPartyUInfo("00");
+        kdf.setPartyVInfo("00");
+        kdf.initialize();
+        kaConfig.setParameters(Set.of(kdf));
+        encConfig2.setKeyAgreementConfigurations(Map.of("DH", kaConfig));
+        
         decryptConfig = new BasicDecryptionConfiguration();
         decryptConfig.setDataKeyInfoCredentialResolver(localKeyInfoResolver);
         decryptConfig.setKEKKeyInfoCredentialResolver(localKeyInfoResolver);
@@ -165,7 +179,7 @@ public class ECDHTest extends XMLObjectBaseTestCase {
         PBKDF2 kdf = new PBKDF2();
         kdf.initialize();
         kaConfig.setParameters(Set.of(kdf));
-        encConfig.setKeyAgreementConfigurations(Map.of("EC", kaConfig));
+        encConfig.setKeyAgreementConfigurations(Map.of("DH", kaConfig));
         
         testRoundtrip(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128, null, EncryptionConstants.ALGO_ID_KEYDERIVATION_PBKDF2);
     }
@@ -198,15 +212,19 @@ public class ECDHTest extends XMLObjectBaseTestCase {
             Assert.assertEquals(encryptedDataOrig.getKeyInfo().getEncryptedKeys().get(0).getEncryptionMethod().getAlgorithm(), expectedKEKAlgo);
         }
         
+        AgreementMethod agreementMethod = null;
+        if (!encryptedDataOrig.getKeyInfo().getEncryptedKeys().isEmpty())  {
+            agreementMethod = encryptedDataOrig.getKeyInfo().getEncryptedKeys().get(0).getKeyInfo().getAgreementMethods().get(0);
+        } else {
+            agreementMethod = encryptedDataOrig.getKeyInfo().getAgreementMethods().get(0);
+        }
+        Assert.assertNotNull(agreementMethod);
+        Assert.assertEquals(agreementMethod.getAlgorithm(), EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH_EXPLICIT_KDF);
+        
         if (expectedKDFAlgo != null) {
-            KeyDerivationMethod kdm = null;
-           if (!encryptedDataOrig.getKeyInfo().getEncryptedKeys().isEmpty())  {
-               kdm = (KeyDerivationMethod) encryptedDataOrig.getKeyInfo().getEncryptedKeys().get(0).getKeyInfo().getAgreementMethods().get(0).getUnknownXMLObjects(KeyDerivationMethod.DEFAULT_ELEMENT_NAME).get(0); 
-           } else {
-               kdm = (KeyDerivationMethod) encryptedDataOrig.getKeyInfo().getAgreementMethods().get(0).getUnknownXMLObjects(KeyDerivationMethod.DEFAULT_ELEMENT_NAME).get(0);
-           }
-           Assert.assertNotNull(kdm);
-           Assert.assertEquals(kdm.getAlgorithm(), expectedKDFAlgo);
+            KeyDerivationMethod kdm = (KeyDerivationMethod) agreementMethod.getUnknownXMLObjects(KeyDerivationMethod.DEFAULT_ELEMENT_NAME).get(0); 
+            Assert.assertNotNull(kdm);
+            Assert.assertEquals(kdm.getAlgorithm(), expectedKDFAlgo);
         }
         
         // Serialize out and back in
diff --git a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/encryption/support/tests/ECDHTest.java b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/encryption/support/tests/DHWithLegacyKDFTest.java
similarity index 74%
copy from opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/encryption/support/tests/ECDHTest.java
copy to opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/encryption/support/tests/DHWithLegacyKDFTest.java
index f102eeafd..9847576d6 100644
--- a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/encryption/support/tests/ECDHTest.java
+++ b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/encryption/support/tests/DHWithLegacyKDFTest.java
@@ -20,7 +20,6 @@ package org.opensaml.xmlsec.encryption.support.tests;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.security.KeyPair;
-import java.security.spec.ECGenParameterSpec;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -35,6 +34,7 @@ import org.opensaml.core.xml.util.XMLObjectSupport;
 import org.opensaml.security.credential.BasicCredential;
 import org.opensaml.security.credential.Credential;
 import org.opensaml.security.credential.impl.CollectionCredentialResolver;
+import org.opensaml.security.crypto.JCAConstants;
 import org.opensaml.security.crypto.KeySupport;
 import org.opensaml.xmlsec.DecryptionConfiguration;
 import org.opensaml.xmlsec.DecryptionParameters;
@@ -42,11 +42,12 @@ import org.opensaml.xmlsec.DecryptionParametersResolver;
 import org.opensaml.xmlsec.EncryptionConfiguration;
 import org.opensaml.xmlsec.EncryptionParameters;
 import org.opensaml.xmlsec.EncryptionParametersResolver;
+import org.opensaml.xmlsec.agreement.impl.DigestMethod;
+import org.opensaml.xmlsec.agreement.impl.KANonce;
 import org.opensaml.xmlsec.criterion.DecryptionConfigurationCriterion;
 import org.opensaml.xmlsec.criterion.EncryptionConfigurationCriterion;
-import org.opensaml.xmlsec.derivation.impl.PBKDF2;
+import org.opensaml.xmlsec.encryption.AgreementMethod;
 import org.opensaml.xmlsec.encryption.EncryptedData;
-import org.opensaml.xmlsec.encryption.KeyDerivationMethod;
 import org.opensaml.xmlsec.encryption.support.DataEncryptionParameters;
 import org.opensaml.xmlsec.encryption.support.Decrypter;
 import org.opensaml.xmlsec.encryption.support.Encrypter;
@@ -61,6 +62,7 @@ import org.opensaml.xmlsec.keyinfo.impl.KeyInfoProvider;
 import org.opensaml.xmlsec.keyinfo.impl.LocalKeyInfoCredentialResolver;
 import org.opensaml.xmlsec.keyinfo.impl.provider.AgreementMethodKeyInfoProvider;
 import org.opensaml.xmlsec.mock.SignableSimpleXMLObject;
+import org.opensaml.xmlsec.signature.support.SignatureConstants;
 import org.opensaml.xmlsec.testing.XMLSecurityTestingSupport;
 import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
@@ -74,7 +76,7 @@ import net.shibboleth.utilities.java.support.xml.SerializeSupport;
 /**
  *
  */
-public class ECDHTest extends XMLObjectBaseTestCase {
+public class DHWithLegacyKDFTest extends XMLObjectBaseTestCase {
     
     private String targetFile;
     
@@ -86,7 +88,7 @@ public class ECDHTest extends XMLObjectBaseTestCase {
     private Encrypter encrypter;
     private EncryptionParametersResolver encParamsResolver;
     private CriteriaSet encCriteria;
-    private BasicEncryptionConfiguration encConfig;
+    private BasicEncryptionConfiguration encConfig, encConfig2;
     
     private DecryptionParametersResolver decryptParamsResolver;
     private CriteriaSet decryptCriteria;
@@ -96,7 +98,7 @@ public class ECDHTest extends XMLObjectBaseTestCase {
     public void beforeClass() throws Exception {
         targetFile = "/org/opensaml/xmlsec/encryption/support/SimpleEncryptionTest.xml";
         
-        KeyPair kp = KeySupport.generateKeyPair("EC", new ECGenParameterSpec("secp256r1"), null);
+        KeyPair kp = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN, 2048, null);
         recipientCredPrivate = new BasicCredential(kp.getPublic(), kp.getPrivate());
         recipientCredPublic = new BasicCredential(kp.getPublic());
         
@@ -115,9 +117,21 @@ public class ECDHTest extends XMLObjectBaseTestCase {
     @BeforeMethod
     public void beforeMethod() throws Exception {
         encConfig = new BasicEncryptionConfiguration();
-        encCriteria = new CriteriaSet(new EncryptionConfigurationCriterion(encConfig,
+        encConfig2 = new BasicEncryptionConfiguration();
+        encCriteria = new CriteriaSet(new EncryptionConfigurationCriterion(encConfig, encConfig2,
                 ConfigurationService.get(EncryptionConfiguration.class)));
         
+        // Configure the middle slot explicitly so that we aren't relying on whichever DH variant the library wide config has.
+        KeyAgreementEncryptionConfiguration kaConfig = new KeyAgreementEncryptionConfiguration();
+        kaConfig.setAlgorithm(EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH);
+        DigestMethod dm = new DigestMethod();
+        dm.setAlgorithm(SignatureConstants.ALGO_ID_DIGEST_SHA256);
+        dm.initialize();
+        KANonce nonce = new KANonce();
+        nonce.initialize();
+        kaConfig.setParameters(Set.of(dm, nonce));
+        encConfig2.setKeyAgreementConfigurations(Map.of("DH", kaConfig));
+        
         decryptConfig = new BasicDecryptionConfiguration();
         decryptConfig.setDataKeyInfoCredentialResolver(localKeyInfoResolver);
         decryptConfig.setKEKKeyInfoCredentialResolver(localKeyInfoResolver);
@@ -130,7 +144,7 @@ public class ECDHTest extends XMLObjectBaseTestCase {
     public void roundtripDirectDataEncryption() throws Exception {
         encConfig.setDataEncryptionCredentials(List.of(recipientCredPublic));
         
-        testRoundtrip(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128, null);
+        testRoundtrip(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128, null, SignatureConstants.ALGO_ID_DIGEST_SHA256, true);
     }
 
     @Test
@@ -138,14 +152,14 @@ public class ECDHTest extends XMLObjectBaseTestCase {
         encConfig.setDataEncryptionCredentials(List.of(recipientCredPublic));
         encConfig.setDataEncryptionAlgorithms(List.of(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128_GCM));
         
-        testRoundtrip(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128_GCM, null);
+        testRoundtrip(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128_GCM, null, SignatureConstants.ALGO_ID_DIGEST_SHA256, true);
     }
 
     @Test
     public void roundtripWithKeyWrap() throws Exception {
         encConfig.setKeyTransportEncryptionCredentials(List.of(recipientCredPublic));
         
-        testRoundtrip(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128, EncryptionConstants.ALGO_ID_KEYWRAP_AES128);
+        testRoundtrip(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128, EncryptionConstants.ALGO_ID_KEYWRAP_AES128, SignatureConstants.ALGO_ID_DIGEST_SHA256, true);
     }
 
     @Test
@@ -154,27 +168,23 @@ public class ECDHTest extends XMLObjectBaseTestCase {
         encConfig.setKeyTransportEncryptionAlgorithms(List.of(EncryptionConstants.ALGO_ID_KEYWRAP_AES256));
         encConfig.setDataEncryptionAlgorithms(List.of(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128_GCM));
         
-        testRoundtrip(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128_GCM, EncryptionConstants.ALGO_ID_KEYWRAP_AES256);
+        testRoundtrip(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128_GCM, EncryptionConstants.ALGO_ID_KEYWRAP_AES256, SignatureConstants.ALGO_ID_DIGEST_SHA256, true);
     }
 
     @Test
-    public void roundtripWithPBKDF2() throws Exception {
+    public void roundtripWithSHA512AndNoNonce() throws Exception {
         encConfig.setDataEncryptionCredentials(List.of(recipientCredPublic));
         
         KeyAgreementEncryptionConfiguration kaConfig = new KeyAgreementEncryptionConfiguration();
-        PBKDF2 kdf = new PBKDF2();
-        kdf.initialize();
-        kaConfig.setParameters(Set.of(kdf));
-        encConfig.setKeyAgreementConfigurations(Map.of("EC", kaConfig));
+        DigestMethod dm = new DigestMethod();
+        dm.setAlgorithm(SignatureConstants.ALGO_ID_DIGEST_SHA512);
+        kaConfig.setParameters(Set.of(dm));
+        encConfig.setKeyAgreementConfigurations(Map.of("DH", kaConfig));
         
-        testRoundtrip(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128, null, EncryptionConstants.ALGO_ID_KEYDERIVATION_PBKDF2);
-    }
-
-    private void testRoundtrip(String expectedDataAlgo, String expectedKEKAlgo) throws Exception {
-        testRoundtrip(expectedDataAlgo, expectedKEKAlgo, null);
+        testRoundtrip(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128, null, SignatureConstants.ALGO_ID_DIGEST_SHA512, false);
     }
     
-    private void testRoundtrip(String expectedDataAlgo, String expectedKEKAlgo, String expectedKDFAlgo) throws Exception {
+    private void testRoundtrip(String expectedDataAlgo, String expectedKEKAlgo, String expectedDigestMethod, boolean nonceExpected) throws Exception {
         // Encrypt
         SignableSimpleXMLObject sxoOrig = (SignableSimpleXMLObject) unmarshallElement(targetFile);
         
@@ -198,15 +208,29 @@ public class ECDHTest extends XMLObjectBaseTestCase {
             Assert.assertEquals(encryptedDataOrig.getKeyInfo().getEncryptedKeys().get(0).getEncryptionMethod().getAlgorithm(), expectedKEKAlgo);
         }
         
-        if (expectedKDFAlgo != null) {
-            KeyDerivationMethod kdm = null;
-           if (!encryptedDataOrig.getKeyInfo().getEncryptedKeys().isEmpty())  {
-               kdm = (KeyDerivationMethod) encryptedDataOrig.getKeyInfo().getEncryptedKeys().get(0).getKeyInfo().getAgreementMethods().get(0).getUnknownXMLObjects(KeyDerivationMethod.DEFAULT_ELEMENT_NAME).get(0); 
-           } else {
-               kdm = (KeyDerivationMethod) encryptedDataOrig.getKeyInfo().getAgreementMethods().get(0).getUnknownXMLObjects(KeyDerivationMethod.DEFAULT_ELEMENT_NAME).get(0);
-           }
-           Assert.assertNotNull(kdm);
-           Assert.assertEquals(kdm.getAlgorithm(), expectedKDFAlgo);
+        AgreementMethod agreementMethod = null;
+        if (!encryptedDataOrig.getKeyInfo().getEncryptedKeys().isEmpty())  {
+            agreementMethod = encryptedDataOrig.getKeyInfo().getEncryptedKeys().get(0).getKeyInfo().getAgreementMethods().get(0);
+        } else {
+            agreementMethod = encryptedDataOrig.getKeyInfo().getAgreementMethods().get(0);
+        }
+        Assert.assertNotNull(agreementMethod);
+        Assert.assertEquals(agreementMethod.getAlgorithm(), EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH);
+        
+        if (expectedDigestMethod != null) {
+            org.opensaml.xmlsec.signature.DigestMethod digestMethod =
+                    (org.opensaml.xmlsec.signature.DigestMethod) agreementMethod
+                    .getUnknownXMLObjects(org.opensaml.xmlsec.signature.DigestMethod.DEFAULT_ELEMENT_NAME).get(0);
+            Assert.assertNotNull(digestMethod);
+            Assert.assertEquals(digestMethod.getAlgorithm(), expectedDigestMethod);
+        }
+        
+        org.opensaml.xmlsec.encryption.KANonce nonce = agreementMethod.getKANonce();
+        if (nonceExpected) {
+            Assert.assertNotNull(nonce);
+            Assert.assertNotNull(nonce.getValue());
+        } else {
+            Assert.assertNull(nonce);
         }
         
         // Serialize out and back in
diff --git a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/encryption/support/tests/ECDHTest.java b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/encryption/support/tests/ECDHTest.java
index f102eeafd..bff58a61e 100644
--- a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/encryption/support/tests/ECDHTest.java
+++ b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/encryption/support/tests/ECDHTest.java
@@ -45,6 +45,7 @@ import org.opensaml.xmlsec.EncryptionParametersResolver;
 import org.opensaml.xmlsec.criterion.DecryptionConfigurationCriterion;
 import org.opensaml.xmlsec.criterion.EncryptionConfigurationCriterion;
 import org.opensaml.xmlsec.derivation.impl.PBKDF2;
+import org.opensaml.xmlsec.encryption.AgreementMethod;
 import org.opensaml.xmlsec.encryption.EncryptedData;
 import org.opensaml.xmlsec.encryption.KeyDerivationMethod;
 import org.opensaml.xmlsec.encryption.support.DataEncryptionParameters;
@@ -198,15 +199,19 @@ public class ECDHTest extends XMLObjectBaseTestCase {
             Assert.assertEquals(encryptedDataOrig.getKeyInfo().getEncryptedKeys().get(0).getEncryptionMethod().getAlgorithm(), expectedKEKAlgo);
         }
         
+        AgreementMethod agreementMethod = null;
+        if (!encryptedDataOrig.getKeyInfo().getEncryptedKeys().isEmpty())  {
+            agreementMethod = encryptedDataOrig.getKeyInfo().getEncryptedKeys().get(0).getKeyInfo().getAgreementMethods().get(0);
+        } else {
+            agreementMethod = encryptedDataOrig.getKeyInfo().getAgreementMethods().get(0);
+        }
+        Assert.assertNotNull(agreementMethod);
+        Assert.assertEquals(agreementMethod.getAlgorithm(), EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES);
+        
         if (expectedKDFAlgo != null) {
-            KeyDerivationMethod kdm = null;
-           if (!encryptedDataOrig.getKeyInfo().getEncryptedKeys().isEmpty())  {
-               kdm = (KeyDerivationMethod) encryptedDataOrig.getKeyInfo().getEncryptedKeys().get(0).getKeyInfo().getAgreementMethods().get(0).getUnknownXMLObjects(KeyDerivationMethod.DEFAULT_ELEMENT_NAME).get(0); 
-           } else {
-               kdm = (KeyDerivationMethod) encryptedDataOrig.getKeyInfo().getAgreementMethods().get(0).getUnknownXMLObjects(KeyDerivationMethod.DEFAULT_ELEMENT_NAME).get(0);
-           }
-           Assert.assertNotNull(kdm);
-           Assert.assertEquals(kdm.getAlgorithm(), expectedKDFAlgo);
+            KeyDerivationMethod kdm = (KeyDerivationMethod) agreementMethod.getUnknownXMLObjects(KeyDerivationMethod.DEFAULT_ELEMENT_NAME).get(0); 
+            Assert.assertNotNull(kdm);
+            Assert.assertEquals(kdm.getAlgorithm(), expectedKDFAlgo);
         }
         
         // Serialize out and back in
diff --git a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/keyinfo/impl/KeyAgreementKeyInfoGeneratorTest.java b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/keyinfo/impl/KeyAgreementKeyInfoGeneratorTest.java
index 89a744fb5..e1bb82a3a 100644
--- a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/keyinfo/impl/KeyAgreementKeyInfoGeneratorTest.java
+++ b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/keyinfo/impl/KeyAgreementKeyInfoGeneratorTest.java
@@ -32,6 +32,8 @@ import org.opensaml.security.crypto.JCAConstants;
 import org.opensaml.security.crypto.KeySupport;
 import org.opensaml.xmlsec.agreement.KeyAgreementCredential;
 import org.opensaml.xmlsec.agreement.impl.BasicKeyAgreementCredential;
+import org.opensaml.xmlsec.agreement.impl.DigestMethod;
+import org.opensaml.xmlsec.agreement.impl.KANonce;
 import org.opensaml.xmlsec.derivation.impl.ConcatKDF;
 import org.opensaml.xmlsec.derivation.impl.PBKDF2;
 import org.opensaml.xmlsec.encryption.AgreementMethod;
@@ -56,12 +58,16 @@ import org.testng.annotations.Test;
 public class KeyAgreementKeyInfoGeneratorTest extends XMLObjectBaseTestCase {
     
     private KeyPair keyPairOriginatorECDH, keyPairRecipientECDH;
+    private KeyPair keyPairOriginatorDiffieHellman, keyPairRecipientDiffieHellman;
     
     private Credential credOriginatorECDH, credRecipientECDH;
+    private Credential credOriginatorDiffieHellman, credRecipientDiffieHellman;
     
     private SecretKey derivedKey;
     
     private KeyAgreementCredential credECDH;
+    private KeyAgreementCredential credDiffieHellmanExplicitKDF;
+    private KeyAgreementCredential credDiffieHellmanLegacyKDF;
     
     private KeyAgreementKeyInfoGeneratorFactory factory;
     
@@ -69,10 +75,14 @@ public class KeyAgreementKeyInfoGeneratorTest extends XMLObjectBaseTestCase {
     public void beforeClass() throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
         keyPairOriginatorECDH = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_EC, new ECGenParameterSpec("secp256r1"), null);
         credOriginatorECDH = new BasicCredential(keyPairOriginatorECDH.getPublic(), keyPairOriginatorECDH.getPrivate());
-        
         keyPairRecipientECDH = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_EC, new ECGenParameterSpec("secp256r1"), null);
         credRecipientECDH = new BasicCredential(keyPairRecipientECDH.getPublic());
         
+        keyPairOriginatorDiffieHellman = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN, 1024, null);
+        credOriginatorDiffieHellman = new BasicCredential(keyPairOriginatorDiffieHellman.getPublic(), keyPairOriginatorDiffieHellman.getPrivate());
+        keyPairRecipientDiffieHellman = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_DIFFIE_HELLMAN, 1024, null);
+        credRecipientDiffieHellman = new BasicCredential(keyPairRecipientDiffieHellman.getPublic());
+        
         derivedKey = KeySupport.generateKey(JCAConstants.KEY_ALGO_AES, 256, null);
     }
     
@@ -81,11 +91,12 @@ public class KeyAgreementKeyInfoGeneratorTest extends XMLObjectBaseTestCase {
         factory = new KeyAgreementKeyInfoGeneratorFactory(); 
         
         credECDH = new BasicKeyAgreementCredential(derivedKey, EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES, credOriginatorECDH, credRecipientECDH);
+        credDiffieHellmanExplicitKDF = new BasicKeyAgreementCredential(derivedKey, EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH_EXPLICIT_KDF, credOriginatorDiffieHellman, credRecipientDiffieHellman);
+        credDiffieHellmanLegacyKDF = new BasicKeyAgreementCredential(derivedKey, EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH, credOriginatorDiffieHellman, credRecipientDiffieHellman);
     }
     
-    
     @Test
-    void ECDHWithConcatKDFWithDefaults() throws Exception {
+    public void ECDHWithConcatKDFWithDefaults() throws Exception {
         ConcatKDF kdf = new ConcatKDF();
         kdf.setDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA512);
         kdf.setAlgorithmID("AA");
@@ -106,6 +117,8 @@ public class KeyAgreementKeyInfoGeneratorTest extends XMLObjectBaseTestCase {
         Assert.assertEquals(keyInfo.getAgreementMethods().size(), 1);
         
         AgreementMethod agreementMethod = keyInfo.getAgreementMethods().get(0);
+        Assert.assertEquals(agreementMethod.getAlgorithm(), credECDH.getAlgorithm());
+                
         Assert.assertEquals(agreementMethod.getOrderedChildren().size(), 3);
         
         //Originator
@@ -143,7 +156,7 @@ public class KeyAgreementKeyInfoGeneratorTest extends XMLObjectBaseTestCase {
     }
 
     @Test
-    void ECDHWithPBKDF2WithDefaults() throws Exception {
+    public void ECDHWithPBKDF2WithDefaults() throws Exception {
         PBKDF2 kdf = new PBKDF2();
         kdf.setIterationCount(1500);
         kdf.setKeyLength(256);
@@ -162,6 +175,8 @@ public class KeyAgreementKeyInfoGeneratorTest extends XMLObjectBaseTestCase {
         Assert.assertEquals(keyInfo.getAgreementMethods().size(), 1);
         
         AgreementMethod agreementMethod = keyInfo.getAgreementMethods().get(0);
+        Assert.assertEquals(agreementMethod.getAlgorithm(), credECDH.getAlgorithm());
+        
         Assert.assertEquals(agreementMethod.getOrderedChildren().size(), 3);
         
         //Originator
@@ -200,6 +215,121 @@ public class KeyAgreementKeyInfoGeneratorTest extends XMLObjectBaseTestCase {
         Assert.assertEquals(kdfParams.getSalt().getSpecified().getValue(), "ABCD");
     }
     
+    @Test
+    public void DiffieHellmanWithConcatKDFWithDefaults() throws Exception {
+        ConcatKDF kdf = new ConcatKDF();
+        kdf.setDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA512);
+        kdf.setAlgorithmID("AA");
+        kdf.setPartyUInfo("BB");
+        kdf.setPartyVInfo("CC");
+        kdf.setSuppPubInfo("DD");
+        kdf.setSuppPrivInfo("EE");
+        kdf.initialize();
+        
+        credDiffieHellmanExplicitKDF.getParameters().add(kdf);
+        
+        KeyInfoGenerator generator = factory.newInstance();
+        KeyInfo keyInfo = generator.generate(credDiffieHellmanExplicitKDF);
+        
+        Assert.assertNotNull(keyInfo);
+        Assert.assertNotNull(keyInfo.getOrderedChildren());
+        Assert.assertEquals(keyInfo.getOrderedChildren().size(), 1);
+        Assert.assertEquals(keyInfo.getAgreementMethods().size(), 1);
+        
+        AgreementMethod agreementMethod = keyInfo.getAgreementMethods().get(0);
+        Assert.assertEquals(agreementMethod.getAlgorithm(), credDiffieHellmanExplicitKDF.getAlgorithm());
+        
+        Assert.assertEquals(agreementMethod.getOrderedChildren().size(), 3);
+        
+        //Originator
+        Assert.assertNotNull(agreementMethod.getOriginatorKeyInfo());
+        OriginatorKeyInfo originatorKeyInfo = agreementMethod.getOriginatorKeyInfo();
+        Assert.assertEquals(originatorKeyInfo.getOrderedChildren().size(), 2);
+        Assert.assertEquals(originatorKeyInfo.getDEREncodedKeyValues().size(), 1);
+        Assert.assertEquals(KeyInfoSupport.getKey(originatorKeyInfo.getDEREncodedKeyValues().get(0)), keyPairOriginatorDiffieHellman.getPublic());
+        Assert.assertEquals(originatorKeyInfo.getKeyValues().size(), 1);
+        Assert.assertEquals(KeyInfoSupport.getKey(originatorKeyInfo.getKeyValues().get(0)), keyPairOriginatorDiffieHellman.getPublic());
+        
+        //Recipient
+        Assert.assertNotNull(agreementMethod.getRecipientKeyInfo());
+        RecipientKeyInfo recipientKeyInfo = agreementMethod.getRecipientKeyInfo();
+        Assert.assertEquals(recipientKeyInfo.getOrderedChildren().size(), 2);
+        Assert.assertEquals(recipientKeyInfo.getDEREncodedKeyValues().size(), 1);
+        Assert.assertEquals(KeyInfoSupport.getKey(recipientKeyInfo.getDEREncodedKeyValues().get(0)), keyPairRecipientDiffieHellman.getPublic());
+        Assert.assertEquals(recipientKeyInfo.getKeyValues().size(), 1);
+        Assert.assertEquals(KeyInfoSupport.getKey(recipientKeyInfo.getKeyValues().get(0)), keyPairRecipientDiffieHellman.getPublic());
+        
+        //Params
+        Assert.assertEquals(agreementMethod.getUnknownXMLObjects(KeyDerivationMethod.DEFAULT_ELEMENT_NAME).size(), 1);
+        KeyDerivationMethod kdm = (KeyDerivationMethod) agreementMethod.getUnknownXMLObjects(KeyDerivationMethod.DEFAULT_ELEMENT_NAME).get(0);
+        Assert.assertEquals(kdm.getAlgorithm(), EncryptionConstants.ALGO_ID_KEYDERIVATION_CONCATKDF);
+        Assert.assertEquals(kdm.getUnknownXMLObjects().size(), 1);
+        Assert.assertEquals(kdm.getUnknownXMLObjects(ConcatKDFParams.DEFAULT_ELEMENT_NAME).size(), 1);
+        ConcatKDFParams kdfParams = (ConcatKDFParams) kdm.getUnknownXMLObjects(ConcatKDFParams.DEFAULT_ELEMENT_NAME).get(0);
+        Assert.assertNotNull(kdfParams.getDigestMethod());
+        Assert.assertEquals(kdfParams.getDigestMethod().getAlgorithm(), SignatureConstants.ALGO_ID_DIGEST_SHA512);
+        Assert.assertEquals(kdfParams.getAlgorithmID(), "00AA");
+        Assert.assertEquals(kdfParams.getPartyUInfo(), "00BB");
+        Assert.assertEquals(kdfParams.getPartyVInfo(), "00CC");
+        Assert.assertEquals(kdfParams.getSuppPubInfo(), "00DD");
+        Assert.assertEquals(kdfParams.getSuppPrivInfo(), "00EE");
+    }
+
+    @Test
+    public void DiffieHellmanWithLegacyKDFWithDefaults() throws Exception {
+        DigestMethod dm = new DigestMethod();
+        dm.setAlgorithm(SignatureConstants.ALGO_ID_DIGEST_SHA512);
+        dm.initialize();
+        
+        KANonce nonce = new KANonce();
+        nonce.setValue("ABCD");
+        nonce.initialize();
+        
+        credDiffieHellmanLegacyKDF.getParameters().add(dm);
+        credDiffieHellmanLegacyKDF.getParameters().add(nonce);
+        
+        KeyInfoGenerator generator = factory.newInstance();
+        KeyInfo keyInfo = generator.generate(credDiffieHellmanLegacyKDF);
+        
+        Assert.assertNotNull(keyInfo);
+        Assert.assertNotNull(keyInfo.getOrderedChildren());
+        Assert.assertEquals(keyInfo.getOrderedChildren().size(), 1);
+        Assert.assertEquals(keyInfo.getAgreementMethods().size(), 1);
+        
+        AgreementMethod agreementMethod = keyInfo.getAgreementMethods().get(0);
+        Assert.assertEquals(agreementMethod.getAlgorithm(), credDiffieHellmanLegacyKDF.getAlgorithm());
+        
+        Assert.assertEquals(agreementMethod.getOrderedChildren().size(), 4);
+        
+        //Originator
+        Assert.assertNotNull(agreementMethod.getOriginatorKeyInfo());
+        OriginatorKeyInfo originatorKeyInfo = agreementMethod.getOriginatorKeyInfo();
+        Assert.assertEquals(originatorKeyInfo.getOrderedChildren().size(), 2);
+        Assert.assertEquals(originatorKeyInfo.getDEREncodedKeyValues().size(), 1);
+        Assert.assertEquals(KeyInfoSupport.getKey(originatorKeyInfo.getDEREncodedKeyValues().get(0)), keyPairOriginatorDiffieHellman.getPublic());
+        Assert.assertEquals(originatorKeyInfo.getKeyValues().size(), 1);
+        Assert.assertEquals(KeyInfoSupport.getKey(originatorKeyInfo.getKeyValues().get(0)), keyPairOriginatorDiffieHellman.getPublic());
+        
+        //Recipient
+        Assert.assertNotNull(agreementMethod.getRecipientKeyInfo());
+        RecipientKeyInfo recipientKeyInfo = agreementMethod.getRecipientKeyInfo();
+        Assert.assertEquals(recipientKeyInfo.getOrderedChildren().size(), 2);
+        Assert.assertEquals(recipientKeyInfo.getDEREncodedKeyValues().size(), 1);
+        Assert.assertEquals(KeyInfoSupport.getKey(recipientKeyInfo.getDEREncodedKeyValues().get(0)), keyPairRecipientDiffieHellman.getPublic());
+        Assert.assertEquals(recipientKeyInfo.getKeyValues().size(), 1);
+        Assert.assertEquals(KeyInfoSupport.getKey(recipientKeyInfo.getKeyValues().get(0)), keyPairRecipientDiffieHellman.getPublic());
+        
+        //Params
+        Assert.assertNotNull(agreementMethod.getKANonce());
+        Assert.assertEquals(agreementMethod.getKANonce().getValue(), "ABCD");
+        Assert.assertEquals(agreementMethod.getUnknownXMLObjects(org.opensaml.xmlsec.signature.DigestMethod.DEFAULT_ELEMENT_NAME).size(), 1);
+        org.opensaml.xmlsec.signature.DigestMethod xmlDigest =
+                (org.opensaml.xmlsec.signature.DigestMethod) agreementMethod.getUnknownXMLObjects(
+                        org.opensaml.xmlsec.signature.DigestMethod.DEFAULT_ELEMENT_NAME).get(0);
+        Assert.assertEquals(xmlDigest.getAlgorithm(), SignatureConstants.ALGO_ID_DIGEST_SHA512);
+    }
+
+    
     @Test
     public void noEmitKeyinfos() throws Exception {
         factory.setEmitOriginatorKeyInfo(false);
diff --git a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/keyinfo/tests/KeyInfoSupportTest.java b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/keyinfo/tests/KeyInfoSupportTest.java
index ced4a8f5a..440344917 100644
--- a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/keyinfo/tests/KeyInfoSupportTest.java
+++ b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/keyinfo/tests/KeyInfoSupportTest.java
@@ -39,6 +39,7 @@ import java.security.spec.X509EncodedKeySpec;
 import java.util.Arrays;
 import java.util.List;
 
+import javax.crypto.interfaces.DHPublicKey;
 import javax.security.auth.x500.X500Principal;
 
 import net.shibboleth.utilities.java.support.codec.Base64Support;
@@ -49,9 +50,13 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.opensaml.core.testing.XMLObjectBaseTestCase;
 import org.opensaml.security.SecurityException;
 import org.opensaml.security.crypto.KeySupport;
+import org.opensaml.security.crypto.dh.DHSupport;
 import org.opensaml.security.crypto.ec.ECSupport;
 import org.opensaml.security.crypto.ec.EnhancedECParameterSpec;
 import org.opensaml.security.x509.X509Support;
+import org.opensaml.xmlsec.encryption.DHKeyValue;
+import org.opensaml.xmlsec.encryption.Generator;
+import org.opensaml.xmlsec.encryption.Public;
 import org.opensaml.xmlsec.keyinfo.KeyInfoSupport;
 import org.opensaml.xmlsec.signature.DEREncodedKeyValue;
 import org.opensaml.xmlsec.signature.DSAKeyValue;
@@ -189,6 +194,14 @@ public class KeyInfoSupportTest extends XMLObjectBaseTestCase {
             + "BkhuSKX/2PbljnmIdGV7mJK9/XUHnyKgZBxXEul2mlvGkrgUvyv+qYsCFsKSSrkB"
             + "1Mj2Ql5xmTMaePMEmvOr6fDAP0OH8cvADEZjx0s/5vvoBFPGGmPrHJluEVS0Fu8I" + "9sROg9YjyuhRV0b8xHo=";
     
+    /** Test DH key 1. */
+    private final String dhPubKey1 = "MIIBJDCBmQYJKoZIhvcNAQMBMIGLAoGBAP//////////yQ/aoiFowjTExmKLgNwc0SkCTgiKZ8x0"
+            + "Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHCReSFtXZiXn7G9ExC6aY37WsL"
+            + "/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7OZTgf//////////AgECAgICAAOBhQACgYEAwICZ"
+            + "ws/L/QcxdYfg9AU/a0y3jEkgn6FaD0eaUTiWcXjpqEeVjPgqEeGnhffxI7z0B5n/ZSNB8bLVjrKe"
+            + "srlS9Opop6HBKW9yuC9bMisN69n0eZn1SJoM3CpX5eBuVx3pOca2vf4T3J1naVpgvDTyhaaZ4rqH"
+            + "3WC34FMOvm3rJio=";
+    
     /** Test EC key with named curve variant 1, curve: secp256r1, OID: 1.2.840.10045.3.1.7 */
     private final String ecPubKey_NamedCurve1 = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBM0jGYrvVMpbVTT728+RfDLL0tPg"
             + "swfUSUXfrXKwAGOmrSbF1KHsErZdXhnEC1VSmm9kTd8VzIi4OihEVMoU+w==";
@@ -220,6 +233,8 @@ public class KeyInfoSupportTest extends XMLObjectBaseTestCase {
 
     private DSAKeyValue xmlDSAKeyValue1, xmlDSAKeyValue1NoParams;
 
+    private DHKeyValue xmlDHKeyValue1;
+
     private RSAKeyValue xmlRSAKeyValue1;
 
     private ECKeyValue xmlECKeyValue_NamedCurve1, xmlECKeyValue_ExplicitParams1;
@@ -236,6 +251,8 @@ public class KeyInfoSupportTest extends XMLObjectBaseTestCase {
 
     private DSAPublicKey javaDSAPubKey1;
     
+    private DHPublicKey javaDHPubKey1;
+    
     private ECPublicKey javaECPubKey_NamedCurve1, javaECPubKey_ExplicitParams1;
 
     private DSAParams javaDSAParams1;
@@ -277,6 +294,7 @@ public class KeyInfoSupportTest extends XMLObjectBaseTestCase {
         javaCRL1 = X509Support.decodeCRL(crl1);
 
         javaDSAPubKey1 = KeySupport.buildJavaDSAPublicKey(dsaPubKey1);
+        javaDHPubKey1 = KeySupport.buildJavaDHPublicKey(dhPubKey1);
         javaRSAPubKey1 = KeySupport.buildJavaRSAPublicKey(rsaPubKey1);
         javaECPubKey_NamedCurve1 = KeySupport.buildJavaECPublicKey(ecPubKey_NamedCurve1);
         // SunEC provider doesn't support explicit params, so use a custom method. 
@@ -290,6 +308,22 @@ public class KeyInfoSupportTest extends XMLObjectBaseTestCase {
         xmlRSAKeyValue1.setModulus(modulus);
         xmlRSAKeyValue1.setExponent(exponent);
 
+        xmlDHKeyValue1 = (DHKeyValue) buildXMLObject(DHKeyValue.DEFAULT_ELEMENT_NAME);
+        org.opensaml.xmlsec.encryption.P dhP =
+                (org.opensaml.xmlsec.encryption.P) buildXMLObject(org.opensaml.xmlsec.encryption.P.DEFAULT_ELEMENT_NAME);
+        org.opensaml.xmlsec.encryption.Q dhQ =
+                (org.opensaml.xmlsec.encryption.Q) buildXMLObject(org.opensaml.xmlsec.encryption.Q.DEFAULT_ELEMENT_NAME);
+        Generator gen = (Generator) buildXMLObject(Generator.DEFAULT_ELEMENT_NAME);
+        Public pub = (Public) buildXMLObject(Public.DEFAULT_ELEMENT_NAME);
+        dhP.setValueBigInt(javaDHPubKey1.getParams().getP());
+        dhQ.setValueBigInt(DHSupport.getPrimeQDomainParameter(javaDHPubKey1));
+        gen.setValueBigInt(javaDHPubKey1.getParams().getG());
+        pub.setValueBigInt(javaDHPubKey1.getY());
+        xmlDHKeyValue1.setP(dhP);
+        xmlDHKeyValue1.setQ(dhQ);
+        xmlDHKeyValue1.setGenerator(gen);
+        xmlDHKeyValue1.setPublic(pub);
+
         xmlDSAKeyValue1 = (DSAKeyValue) buildXMLObject(DSAKeyValue.DEFAULT_ELEMENT_NAME);
         P p = (P) buildXMLObject(P.DEFAULT_ELEMENT_NAME);
         Q q = (Q) buildXMLObject(Q.DEFAULT_ELEMENT_NAME);
@@ -457,7 +491,7 @@ public class KeyInfoSupportTest extends XMLObjectBaseTestCase {
         Assert.assertEquals(X509Support.decodeCRL(xmlCRL.getValue()), javaCRL1,
                 "Java X509CRL encoding to XMLObject failed");
     }
-
+    
     /** Test conversion of DSA public keys from XML to Java security native type. */
     @Test
     public void testDSAConversionXMLToJava() {
@@ -490,6 +524,22 @@ public class KeyInfoSupportTest extends XMLObjectBaseTestCase {
         }
     }
 
+    /** Test conversion of DH public keys from XML to Java security native type. */
+    @Test
+    public void testDHConversionXMLToJava() {
+        PublicKey key = null;
+        DHPublicKey dhKey = null;
+
+        try {
+            key = KeyInfoSupport.getDHKey(xmlDHKeyValue1);
+        } catch (KeyException e) {
+            Assert.fail("DH key conversion XML to Java failed: " + e);
+        }
+        dhKey = (DHPublicKey) key;
+        Assert.assertNotNull(dhKey, "Generated key was not an instance of DHPublicKey");
+        Assert.assertEquals(dhKey, javaDHPubKey1, "Generated key was not the expected value");
+    }
+
     /** Test conversion of RSA public keys from XML to Java security native type. */
     @Test
     public void testRSAConversionXMLToJava() {
@@ -545,6 +595,22 @@ public class KeyInfoSupportTest extends XMLObjectBaseTestCase {
 
     }
 
+    /** Test conversion of DH public keys from Java security native type to XML. 
+     * @throws EncodingException on base64 encoding error*/
+    @Test
+    public void testDHConversionJavaToXML() throws EncodingException {
+        DHKeyValue dhKeyValue = KeyInfoSupport.buildDHKeyValue(javaDHPubKey1);
+        Assert.assertNotNull(dhKeyValue);
+        Assert.assertEquals(dhKeyValue
+                .getPublic().getValueBigInt(), javaDHPubKey1.getY(), "Generated DHKeyValue Public component was not the expected value");
+        Assert.assertEquals(dhKeyValue.getP().getValueBigInt(), javaDHPubKey1.getParams().getP(),
+                "Generated DHKeyValue P component was not the expected value");
+        Assert.assertEquals(dhKeyValue.getGenerator().getValueBigInt(), javaDHPubKey1.getParams().getG(),
+                "Generated DHKeyValue Generator component was not the expected value");
+        Assert.assertEquals(dhKeyValue.getQ().getValueBigInt(), DHSupport.getPrimeQDomainParameter(javaDHPubKey1),
+                "Generated DHKeyValue Q component was not the expected value");
+    }
+
     /** Test conversion of DSA public keys from Java security native type to XML. 
      * @throws EncodingException on base64 encoding error*/
     @Test
@@ -601,6 +667,26 @@ public class KeyInfoSupportTest extends XMLObjectBaseTestCase {
                 javaECPubKey_ExplicitParams1.getW());
     }
 
+    /** Tests extracting a DH public key from a KeyValue. */
+    @Test
+    public void testGetDHKey() {
+        keyValue.setRSAKeyValue(null);
+        keyValue.setDHKeyValue(xmlDHKeyValue1);
+
+        PublicKey pk = null;
+        DHPublicKey dhKey = null;
+        try {
+            pk = KeyInfoSupport.getKey(keyValue);
+        } catch (KeyException e) {
+            Assert.fail("Extraction of key from KeyValue failed: " + e);
+        }
+        Assert.assertTrue(pk instanceof DHPublicKey, "Generated key was not an instance of DHPublicKey");
+        dhKey = (DHPublicKey) pk;
+        Assert.assertEquals(dhKey, javaDHPubKey1, "Generated key was not the expected value");
+
+        keyValue.setDSAKeyValue(null);
+    }
+
     /** Tests extracting a DSA public key from a KeyValue. */
     @Test
     public void testGetDSAKey() {
@@ -665,6 +751,30 @@ public class KeyInfoSupportTest extends XMLObjectBaseTestCase {
         keyInfo.getKeyValues().clear();
     }    
 
+    /** Tests adding a public key as a KeyValue to KeyInfo. 
+     * @throws EncodingException on base64 encoding error*/
+    @Test
+    public void testAddDHPublicKey() throws EncodingException {
+        keyInfo.getKeyValues().clear();
+
+        KeyInfoSupport.addPublicKey(keyInfo, javaDHPubKey1);
+        KeyValue kv = keyInfo.getKeyValues().get(0);
+        Assert.assertNotNull(kv, "KeyValue was null");
+        DHKeyValue dhKeyValue = kv.getDHKeyValue();
+        Assert.assertNotNull(dhKeyValue, "DHKeyValue was null");
+
+        DHPublicKey javaKey = null;
+        try {
+            javaKey = (DHPublicKey) KeyInfoSupport.getDHKey(dhKeyValue);
+        } catch (KeyException e) {
+            Assert.fail("Extraction of Java key failed: " + e);
+        }
+
+        Assert.assertEquals(javaKey, javaDHPubKey1, "Inserted DH public key was not the expected value");
+
+        keyInfo.getKeyValues().clear();
+    }    
+
     /** Tests adding a public key as a KeyValue to KeyInfo. 
      * @throws EncodingException on base64 encoding error*/
     @Test
@@ -765,7 +875,7 @@ public class KeyInfoSupportTest extends XMLObjectBaseTestCase {
             Assert.fail("Extraction of Java key failed: " + e);
         }
         
-        Assert.assertEquals(javaDSAPubKey1, javaKey, "Inserted RSA public key was not the expected value");
+        Assert.assertEquals(javaDSAPubKey1, javaKey, "Inserted DSA public key was not the expected value");
         
         keyInfo.getDEREncodedKeyValues().clear();
     }
diff --git a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/testing/XMLSecurityTestingSupport.java b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/testing/XMLSecurityTestingSupport.java
index 1ae4500a7..f4ffcfe40 100644
--- a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/testing/XMLSecurityTestingSupport.java
+++ b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/testing/XMLSecurityTestingSupport.java
@@ -23,6 +23,7 @@ import java.util.List;
 import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver;
 import org.opensaml.xmlsec.keyinfo.impl.BasicProviderKeyInfoCredentialResolver;
 import org.opensaml.xmlsec.keyinfo.impl.KeyInfoProvider;
+import org.opensaml.xmlsec.keyinfo.impl.provider.DEREncodedKeyValueProvider;
 import org.opensaml.xmlsec.keyinfo.impl.provider.DSAKeyValueProvider;
 import org.opensaml.xmlsec.keyinfo.impl.provider.ECKeyValueProvider;
 import org.opensaml.xmlsec.keyinfo.impl.provider.InlineX509DataProvider;
@@ -50,6 +51,7 @@ public final class XMLSecurityTestingSupport {
         providers.add( new RSAKeyValueProvider() );
         providers.add( new DSAKeyValueProvider() );
         providers.add( new ECKeyValueProvider() );
+        providers.add( new DEREncodedKeyValueProvider() );
         providers.add( new InlineX509DataProvider() );
         return providers;
     }
diff --git a/opensaml-xmlsec-impl/src/test/resources/logback-test.xml b/opensaml-xmlsec-impl/src/test/resources/logback-test.xml
index b031592ce..897e843c9 100644
--- a/opensaml-xmlsec-impl/src/test/resources/logback-test.xml
+++ b/opensaml-xmlsec-impl/src/test/resources/logback-test.xml
@@ -8,6 +8,10 @@
         </encoder>
     </appender>
     
+    <logger name="org.opensaml.xmlsec.derivation.impl.DHLegacyKDF">
+        <level value="TRACE"/>
+    </logger>
+    
     <logger name="org.apache.xml.security.encryption.XMLCipher">
         <level value="TRACE"/>
     </logger>

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list