Skip to content

Commit 2ee7317

Browse files
committed
Merge branch 'sm9-keyagreement' into 'main'
SM9: expose the key exchange as KeyAgreement.SM9, on the KeyAgreement.SM2 model See merge request root/bc-java!304
2 parents 7a629b3 + 8bc2083 commit 2ee7317

25 files changed

Lines changed: 896 additions & 23 deletions

core/src/main/java/org/bouncycastle/crypto/agreement/SM9KeyExchange.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public ECPoint generateEphemeral(SecureRandom random)
7575
return ephemeralPoint;
7676
}
7777

78+
7879
/**
7980
* Compute the shared key of {@code klenBits} bits from the peer's ephemeral
8081
* value {@code peerR}. Must be called after {@link #generateEphemeral}.

core/src/main/java/org/bouncycastle/crypto/params/SM9EncPrivateKeyParameters.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ public byte[] getEncoded()
9898
}
9999

100100
/**
101-
* Rebuild a KEM / decryption user key from its bare point encoding.
101+
* Rebuild a KEM / decryption user key from its bare point encoding. There is
102+
* deliberately no key-exchange counterpart: nothing needs one yet, and an
103+
* exchange user key is obtained from the master key's derivation instead.
102104
*/
103105
public static SM9EncPrivateKeyParameters fromEncoded(
104106
byte[] enc, SM9EncMasterPublicKeyParameters masterPublicKey, byte[] identity, byte hid)
@@ -107,15 +109,6 @@ public static SM9EncPrivateKeyParameters fromEncoded(
107109
return new SM9EncPrivateKeyParameters(SM9G2Point.decode(enc), masterPublicKey, Arrays.clone(identity), hid, false);
108110
}
109111

110-
/**
111-
* Rebuild a key-exchange user key from its bare point encoding.
112-
*/
113-
public static SM9EncPrivateKeyParameters fromEncodedExchangeKey(
114-
byte[] enc, SM9EncMasterPublicKeyParameters masterPublicKey, byte[] identity, byte hid)
115-
{
116-
SM9EncMasterPrivateKeyParameters.checkHid(hid);
117-
return new SM9EncPrivateKeyParameters(SM9G2Point.decode(enc), masterPublicKey, Arrays.clone(identity), hid, true);
118-
}
119112

120113
/**
121114
* Destroy this object, dropping its reference to the private point de and

docs/releasenotes.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ <h3>2.1.3 Additional Features and Functionality</h3>
9393
<li>generateCbom (gradle/cbom.gradle) now inventories the public lightweight API as well as the JCA service tables: the org.bouncycastle.crypto.* packages bundled in the bcprov jar are swept for algorithms that ship with no JCA registration, so J-PAKE, EC J-PAKE, SRP-6a, OWL, HPKE, BLS12-381, RSA-KEM, ECIES-KEM, ECCSI, SAKKE, BIP340, MQV, the SP800-90A DRBGs (including the deprecated Dual EC DRBG), Argon2, bcrypt, the KDF1/KDF2/SP800-108 derivation functions, cSHAKE, SHA-512/t and the NIST lightweight-cryptography finalists (Ascon - including the deprecated pre-standard Ascon-128/128a/80pq forms alongside Ascon-AEAD128 - Elephant, GIFT-COFB, Grain-128 AEAD, ISAP, Photon-Beetle, Romulus, Schwaemm/Sparkle and Xoodyak, each parameterised variant as its own asset) appear in the CBOM. Public concrete implementations of the core primitive interfaces become assets automatically - a class is treated as a generic construction rather than an algorithm only when a public constructor takes another core primitive (HMac(Digest), CMac(BlockCipher)) - so a newly added public lightweight algorithm cannot silently miss the inventory. Every asset now carries a bc:api property recording whether it is reachable through the JCA providers (jca) or only through the lightweight API (lightweight), and lightweight assets name their defining classes in a bc:classes property.</li>
9494
<li>SM2Engine.decrypt and the GOST28147, DSTU7624, DESede and RC2 key-wrap engines sized their output as new byte[inLen - overhead] without first checking the ciphertext was at least that overhead, so a short attacker-supplied ciphertext or wrapped key threw NegativeArraySizeException / ArrayIndexOutOfBoundsException instead of the declared InvalidCipherTextException. The engines now reject an under-length input with InvalidCipherTextException, matching the guard the IES, RFC 3394 and RFC 5649 engines already have.</li>
9595
<li>Four more length-validation guards across the lightweight crypto API reject wrong-length or truncated input up front rather than leaking an unchecked exception; valid-length input is unaffected. The AIMer (org.bouncycastle.pqc.crypto.aimer) private- and public-key parameter constructors reject keyData whose length is not the parameter set's secret / public key size, with IllegalArgumentException. LMSSignature.getInstance rejects trailing data after an LMS signature, making the parse non-malleable. ISO9796d2Signer guards its verify against an RSA-recovered block shorter than the header it must contain, rather than indexing past it. DANEEntry.isValidCertificate (org.bouncycastle.cert.dane) guards against a short or null DNS record rather than throwing ArrayIndexOutOfBoundsException.</li>
96-
<li>Added support for the SM9 identity-based cryptographic algorithms (GM/T 0044-2016), built on an R-ate pairing over a 256-bit Barreto-Naehrig curve: the digital signature algorithm, key encapsulation mechanism, public-key encryption and the key exchange protocol. SM9 is identity-based - a trusted Key Generation Centre (KGC) holds a master key pair per scheme and derives each user's key pair deterministically from the user's identity, so there are no certificates: a user's key pair comes from the master private key via the org.bouncycastle.jcajce.interfaces SM9SigUserKeyGenerator generateUserKeyPair(id) / SM9EncUserKeyGenerator generateUserKeyPair(id, hid) capability interfaces (implemented by SM9SigMasterPrivateKey and SM9EncMasterPrivateKey - the encryption-side hid is the KGC's published private-key generation function identifier, 0x03 for KEM/encryption and 0x02 for key exchange), and a verifier or sender forms the counterparty's public key from the published master public key and the identity alone via SM9SigMasterPublicKey / SM9EncMasterPublicKey getUserPublicKey(id) - so no AlgorithmParameterSpec is needed, the identity travelling in the keys (the model carries inherent key escrow, as the KGC can derive every user's key). The lightweight implementations follow the standard BouncyCastle package layout: SM9Signer in org.bouncycastle.crypto.signers, SM9KEMGenerator / SM9KEMExtractor in org.bouncycastle.crypto.kems, the SM9Engine public-key cipher in org.bouncycastle.crypto.engines, SM9KeyExchange in org.bouncycastle.crypto.agreement, the master key-pair generators in org.bouncycastle.crypto.generators and the key parameter classes in org.bouncycastle.crypto.params, with the BN curve, extension-field tower and R-ate pairing arithmetic in org.bouncycastle.math.ec.sm9. The BouncyCastle provider exposes the algorithms through the GM family as Signature.SM9, Cipher.SM9 and KeyGenerator.SM9-KEM (and, on JDK 21+, KEM.SM9-KEM through the javax.crypto.KEM API), with master key pairs from KeyPairGenerator.SM9-SIGN / SM9-ENC and both schemes' master keys round-tripping through KeyFactory.SM9. Public-key encryption offers the two GM/T 0044.4 data-encapsulation modes - the SM4 block cipher (Cipher.SM9) and a KDF stream cipher (SM9/XOR/NoPadding) - and emits the self-describing GM/T 0080-2020 SM9Cipher structure; the KEM produces the GM/T 0044.4 KDF output at the requested size as the shared secret (the interoperable form, with an optional KTSParameterSpec KDF layer for generic use). The key exchange is a stateful two-party protocol with roles and key confirmation, so it is provided through the lightweight API only. The SM9 private keys honour the JCA javax.security.auth.Destroyable contract, key material is encoded per GM/T 0080-2020, and all algorithms are verified against the official GM/T 0044.5-2016 worked examples.</li>
96+
<li>Added support for the SM9 identity-based cryptographic algorithms (GM/T 0044-2016), built on an R-ate pairing over a 256-bit Barreto-Naehrig curve: the digital signature algorithm, key encapsulation mechanism, public-key encryption and the key exchange protocol. SM9 is identity-based - a trusted Key Generation Centre (KGC) holds a master key pair per scheme and derives each user's key pair deterministically from the user's identity, so there are no certificates: a user's key pair comes from the master private key via the org.bouncycastle.jcajce.interfaces SM9SigUserKeyGenerator generateUserKeyPair(id) / SM9EncUserKeyGenerator generateUserKeyPair(id, hid) capability interfaces (implemented by SM9SigMasterPrivateKey and SM9EncMasterPrivateKey - the encryption-side hid is the KGC's published private-key generation function identifier, 0x03 for KEM/encryption and 0x02 for key exchange), and a verifier or sender forms the counterparty's public key from the published master public key and the identity alone via SM9SigMasterPublicKey / SM9EncMasterPublicKey getUserPublicKey(id) - so no AlgorithmParameterSpec is needed, the identity travelling in the keys (the model carries inherent key escrow, as the KGC can derive every user's key). The lightweight implementations follow the standard BouncyCastle package layout: SM9Signer in org.bouncycastle.crypto.signers, SM9KEMGenerator / SM9KEMExtractor in org.bouncycastle.crypto.kems, the SM9Engine public-key cipher in org.bouncycastle.crypto.engines, SM9KeyExchange in org.bouncycastle.crypto.agreement, the master key-pair generators in org.bouncycastle.crypto.generators and the key parameter classes in org.bouncycastle.crypto.params, with the BN curve, extension-field tower and R-ate pairing arithmetic in org.bouncycastle.math.ec.sm9. The BouncyCastle provider exposes the algorithms through the GM family as Signature.SM9, Cipher.SM9 and KeyGenerator.SM9-KEM (and, on JDK 21+, KEM.SM9-KEM through the javax.crypto.KEM API), with master key pairs from KeyPairGenerator.SM9-SIGN / SM9-ENC and both schemes' master keys round-tripping through KeyFactory.SM9. Public-key encryption offers the two GM/T 0044.4 data-encapsulation modes - the SM4 block cipher (Cipher.SM9) and a KDF stream cipher (SM9/XOR/NoPadding) - and emits the self-describing GM/T 0080-2020 SM9Cipher structure; the KEM produces the GM/T 0044.4 KDF output at the requested size as the shared secret (the interoperable form, with an optional KTSParameterSpec KDF layer for generic use). The key exchange is also available as KeyAgreement.SM9. The protocol is two-round, so it uses the KeyAgreement API's two-phase form: a party initialises with its own key-exchange user key from SM9EncMasterPrivateKey.generateExchangeKeyPair(identity) and an org.bouncycastle.jcajce.spec.SM9KeyExchangeSpec giving the role and the agreed key length, the first doPhase names the peer through its identity-derived public key from SM9EncMasterPublicKey.getUserPublicKey(identity, hid) and returns that party's own ephemeral value to send, and the last doPhase consumes the peer's, wrapped through SM9EncMasterPublicKey.getExchangeEphemeral(encoded). The hid values the GM/T 0044 examples publish are available as SM9EncMasterPublicKey.HID / HID_EXCHANGE and on SM9EncUserKeyGenerator, so a JCA caller names a hid without reaching into the lightweight parameter classes. The ephemeral is generated inside the provider under the master public key carried on the party's own user key, so there is no separate key-pair generation step and an ephemeral cannot be mis-bound to a different master key; the peer key is checked to carry the same hid and master public key. The optional GM/T 0044.3 key-confirmation tags S_A / S_B have no channel in the KeyAgreement API and remain available from the lightweight org.bouncycastle.crypto.agreement.SM9KeyExchange only, which also serves callers wanting the protocol object directly. The SM9 private keys honour the JCA javax.security.auth.Destroyable contract, key material is encoded per GM/T 0080-2020, and all algorithms are verified against the official GM/T 0044.5-2016 worked examples.</li>
9797
<li>The GM/T 0081-2020 SM9 encryption and signature message syntax content types are now available as OID constants on org.bouncycastle.asn1.gm.GMObjectIdentifiers - sm9_pkcs7 (arc 1.2.156.10197.6.1.4.4) and its data, signedData, envelopedData, signedAndEnvelopedData, encryptedData and keyAgreementInfo branches - the SM9 counterpart of the GM/T 0010-2012 sm2_pkcs7 constants already present. Note that, unlike the SM2 arc, the structures these OIDs name are modelled on PKCS#7 but are not interchangeable with it: a GM/T 0081 SignedData keeps the PKCS#7 field order and tags while replacing certificates [0] / crls [1] with ibcSysParamsPublishInfos [0] / irls [1] - so org.bouncycastle.asn1.pkcs.SignedData parses it and silently mislabels those two sets - and its SignerInfo identifies the signer by an identity-based Identifier rather than an IssuerAndSerialNumber and carries an SM9Signature where PKCS#7 has an EncryptedDigest OCTET STRING.</li>
9898
<li>Added support for RFC 9850, the SSLKEYLOGFILE format, so that a capture of a test TLS connection can be decrypted by an analyser such as Wireshark. BouncyCastle reports the secrets and does not store them: an application implements the new org.bouncycastle.tls.keylog.TlsKeyLog interface, whose single log(label, clientRandom, secret) method receives one RFC 9850 sec. 2 record at a time, and decides for itself on encoding, destination and access control (the RFC's labels are available as constants in org.bouncycastle.tls.keylog.TlsKeyLogLabel). The implementation is named by the org.bouncycastle.tls.keylog.class property in the JVM's java.security file, read as a security property rather than a system property because the security properties are an administrator's configuration channel rather than the ambient namespace any library writes to. That property is not itself a privilege boundary - Security.setProperty is only permission-checked under a SecurityManager, disabled by default from JDK 17 and unavailable from JDK 24 - so the boundary that holds is the artifact, not the setting. The named class must be public, implement TlsKeyLog, have a public no-argument constructor and be on the application's class path; its type is checked before it is initialised or constructed, so naming some other class is not a way to have arbitrary code run. It is resolved once, on the first secret of the first handshake, and if the property is unset nothing is loaded and no secret leaves the library. Reported are CLIENT_RANDOM (the master secret) for (D)TLS 1.2 and earlier, on full and resumed handshakes alike, and CLIENT_HANDSHAKE_TRAFFIC_SECRET, SERVER_HANDSHAKE_TRAFFIC_SECRET, CLIENT_TRAFFIC_SECRET_0, SERVER_TRAFFIC_SECRET_0 and EXPORTER_SECRET for TLS 1.3; the early-data labels CLIENT_EARLY_TRAFFIC_SECRET and EARLY_EXPORTER_SECRET are wired to the key schedule but cannot yet be reached, and the ECH labels of RFC 9850 sec. 2.3 do not apply as ECH is not implemented. Because the reporting sits in the key schedule itself, it covers the low-level (D)TLS API and the BCJSSE provider equally. This capability ships only in a new artifact, bctls-klog, which is the TLS API built with key logging present; the standard bctls jar contains none of it and no property will give it any. RFC 9850 sec. 1.1 asks that a deployed binary not be able to disclose its own keys at all, and recommends conditional compilation to that end - a separate artifact is the equivalent for Java, so obtaining connection secrets requires deliberately replacing bctls with bctls-klog, which must not be done in production. bctls-klog keeps the module name and packages of bctls (adding only the exported org.bouncycastle.tls.keylog) so that it drops straight in, and for the same reason it is deliberately absent from the BOM: the two are alternatives, not companions.</li>
9999
<li>KeyPurposeId constants for the three Extended Key Usage KeyPurposeIds defined in RFC 9509 sec. 3 for 5G Network Functions: id_kp_jwt (id-kp 37, signing the JWT Claims Set of a Client Credentials Assertion using JWS), id_kp_httpContentEncrypt (id-kp 38, encrypting JSON objects in HTTP messages between Security Edge Protection Proxies using JWE) and id_kp_oauthAccessTokenSigning (id-kp 39, signing OAuth 2.0 access tokens for service authorization using JWS, as issued by a Network Repository Function). The matching human-readable names are also registered in X509CertificateFormatter so the new EKUs print symbolically.</li>

misc/src/main/java/org/bouncycastle/jcajce/examples/SM9CipherExample.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import javax.crypto.Cipher;
1010

11-
import org.bouncycastle.crypto.params.SM9EncMasterPrivateKeyParameters;
1211
import org.bouncycastle.jcajce.interfaces.SM9EncMasterPrivateKey;
1312
import org.bouncycastle.jcajce.interfaces.SM9EncMasterPublicKey;
1413
import org.bouncycastle.jce.provider.BouncyCastleProvider;
@@ -54,7 +53,7 @@ public static void main(String[] args)
5453
// the private half.
5554
byte[] bobIdentity = Strings.toByteArray("Bob");
5655
KeyPair bob = ((SM9EncMasterPrivateKey)master.getPrivate()).generateUserKeyPair(bobIdentity,
57-
SM9EncMasterPrivateKeyParameters.HID);
56+
SM9EncMasterPublicKey.HID);
5857

5958
byte[] message = Strings.toByteArray("Chinese IBE standard");
6059

misc/src/main/java/org/bouncycastle/jcajce/examples/SM9Example.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
import javax.crypto.KeyGenerator;
1414

15-
import org.bouncycastle.crypto.params.SM9EncMasterPrivateKeyParameters;
1615
import org.bouncycastle.jcajce.SecretKeyWithEncapsulation;
1716
import org.bouncycastle.jcajce.interfaces.SM9EncMasterPrivateKey;
1817
import org.bouncycastle.jcajce.interfaces.SM9EncMasterPublicKey;
@@ -58,7 +57,7 @@ public static void main(String[] args)
5857
// private key (deterministic).
5958
byte[] bobIdentity = Strings.toByteArray("Bob");
6059
KeyPair bob = ((SM9EncMasterPrivateKey)master.getPrivate()).generateUserKeyPair(bobIdentity,
61-
SM9EncMasterPrivateKeyParameters.HID);
60+
SM9EncMasterPublicKey.HID);
6261

6362
// 3. Sender: derive Bob's public key from the published master public key and
6463
// his identity - no certificate or KGC interaction needed - and encapsulate

0 commit comments

Comments
 (0)