Merge pull request 'PQ MLKEM hybrid ratchet only' (#517) from zzz/i2p.i2p:pq-ratchet into master

Reviewed-on: I2P_Developers/i2p.i2p#517
Reviewed-by: idk <idki2p@mail.i2p>
This commit is contained in:
zzz
2025-06-24 08:31:49 -04:00
76 changed files with 5944 additions and 73 deletions

View File

@ -128,6 +128,10 @@ Public domain except as listed below:
Copyright (C) 2006 The Android Open Source Project
See licenses/LICENSE-Apache2.0.txt
ML-KEM:
Copyright (c) 2000-2023 The Legion Of The Bouncy Castle Inc. (https://www.bouncycastle.org)
See licenses/LICENSE-Bouncycastle.txt
Installer:
(not included in distribution packages)

View File

@ -604,6 +604,9 @@
<%
boolean has0 = editBean.hasEncType(curTunnel, 0);
boolean has4 = editBean.hasEncType(curTunnel, 4);
boolean has5 = editBean.hasEncType(curTunnel, 5);
boolean has6 = editBean.hasEncType(curTunnel, 6);
boolean has7 = editBean.hasEncType(curTunnel, 7);
%>
<tr>
<th colspan="2" <%=ehdisabled%>>
@ -617,7 +620,25 @@
<option title="<%=intl._t("This is the default, recommended option")%>" value="4" <%=((has4 && !has0) ? " selected=\"selected\"" : edisabled)%> >
ECIES-X25519</option>
<option value="4,0" <%=((has0 && has4) ? " selected=\"selected\"" : edisabled)%> >
<%=intl._t("Both encryption types")%></option>
ECIES-X25519 + ElGamal-2048</option>
<%
if (editBean.isAdvanced()) {
%>
<option title="<%=intl._t("experimental")%>" value="5" <%=((has5 && !has4) ? " selected=\"selected\"" : edisabled)%> >
MLKEM512-X25519</option>
<option title="<%=intl._t("experimental")%>" value="6" <%=((has6 && !has4) ? " selected=\"selected\"" : edisabled)%> >
MLKEM768-X25519</option>
<option title="<%=intl._t("experimental")%>" value="7" <%=((has7 && !has4) ? " selected=\"selected\"" : edisabled)%> >
MLKEM1024-X25519</option>
<option title="<%=intl._t("experimental")%>" value="5,4" <%=((has5 && has4) ? " selected=\"selected\"" : edisabled)%> >
MLKEM512-X25519 + ECIES-X25519</option>
<option title="<%=intl._t("experimental")%>" value="6,4" <%=((has6 && has4) ? " selected=\"selected\"" : edisabled)%> >
MLKEM768-X25519 + ECIES-X25519</option>
<option title="<%=intl._t("experimental")%>" value="7,4" <%=((has7 && has4) ? " selected=\"selected\"" : edisabled)%> >
MLKEM1024-X25519 + ECIES-X25519</option>
<%
} // isAdvanced()
%>
</select>
</td>
</tr>

View File

@ -676,6 +676,9 @@
<%
boolean has0 = editBean.hasEncType(curTunnel, 0);
boolean has4 = editBean.hasEncType(curTunnel, 4);
boolean has5 = editBean.hasEncType(curTunnel, 5);
boolean has6 = editBean.hasEncType(curTunnel, 6);
boolean has7 = editBean.hasEncType(curTunnel, 7);
String edisabled = canChangeEncType ? "" : " disabled=\"disabled\" ";
%>
<tr>
@ -690,7 +693,25 @@
<option title="<%=intl._t("This is the default, recommended option")%>" value="4" <%=((has4 && !has0) ? " selected=\"selected\"" : edisabled)%> >
ECIES-X25519</option>
<option value="4,0" <%=((has0 && has4) ? " selected=\"selected\"" : edisabled)%> >
<%=intl._t("Both encryption types")%></option>
ECIES-X25519 + ElGamal-2048</option>
<%
if (editBean.isAdvanced()) {
%>
<option title="<%=intl._t("experimental")%>" value="5" <%=((has5 && !has4) ? " selected=\"selected\"" : edisabled)%> >
MLKEM512-X25519</option>
<option title="<%=intl._t("experimental")%>" value="6" <%=((has6 && !has4) ? " selected=\"selected\"" : edisabled)%> >
MLKEM768-X25519</option>
<option title="<%=intl._t("experimental")%>" value="7" <%=((has7 && !has4) ? " selected=\"selected\"" : edisabled)%> >
MLKEM1024-X25519</option>
<option title="<%=intl._t("experimental")%>" value="5,4" <%=((has5 && has4) ? " selected=\"selected\"" : edisabled)%> >
MLKEM512-X25519 + ECIES-X25519</option>
<option title="<%=intl._t("experimental")%>" value="6,4" <%=((has6 && has4) ? " selected=\"selected\"" : edisabled)%> >
MLKEM768-X25519 + ECIES-X25519</option>
<option title="<%=intl._t("experimental")%>" value="7,4" <%=((has7 && has4) ? " selected=\"selected\"" : edisabled)%> >
MLKEM1024-X25519 + ECIES-X25519</option>
<%
} // isAdvanced()
%>
</select>
</td>
</tr>

View File

@ -887,7 +887,7 @@
windowtitle="I2P Anonymous Network - Java Documentation - API Version ${api.version}">
<group title="Core SDK (i2p.jar)" packages="net.i2p:net.i2p.*:net.i2p.client:net.i2p.client.*:net.i2p.internal:net.i2p.internal.*:freenet.support.CPUInformation:gnu.crypto.*:gnu.getopt:gnu.gettext:com.nettgryppa.security:net.i2p.apache.http.conn.ssl:net.i2p.apache.http.conn.util:net.i2p.apache.http.util:org.json.simple:com.southernstorm.noise.crypto.x25519:com.southernstorm.noise.crypto.chacha20:org.minidns:org.minidns.*" />
<group title="Streaming Library" packages="net.i2p.client.streaming:net.i2p.client.streaming.impl" />
<group title="Router" packages="net.i2p.router:net.i2p.router.*:net.i2p.data.i2np:net.i2p.data.router:org.cybergarage:org.cybergarage.*:org.freenetproject:org.xlattice.crypto.filters:com.maxmind.*:com.southernstorm.noise.*" />
<group title="Router" packages="net.i2p.router:net.i2p.router.*:net.i2p.data.i2np:net.i2p.data.router:org.cybergarage:org.cybergarage.*:org.freenetproject:org.xlattice.crypto.filters:com.maxmind.*:com.southernstorm.noise.*:org.bouncycastle:org.bouncycastle.*" />
<group title="Router Console" packages="net.i2p.router.web:net.i2p.router.web.*:net.i2p.router.update:edu.internet2.ndt:net.i2p.router.news:com.vuze.*" />
<!-- apps and bridges starting here, alphabetical please -->
<group title="Addressbook Application" packages="net.i2p.addressbook:net.i2p.router.naming:net.metanotion:net.metanotion.*" />

View File

@ -11,7 +11,14 @@ public enum EncAlgo {
EC("EC"),
/** @since 0.9.38 */
ECIES("ECIES");
ECIES("ECIES"),
/** @since 0.9.67 */
ECIES_MLKEM("ECIES-MLKEM"),
/** @since 0.9.67 */
ECIES_MLKEM_INT("ECIES-MLKEM-Internal");
private final String name;

View File

@ -52,14 +52,83 @@ public enum EncType {
* Pubkey 32 bytes; privkey 32 bytes
* @since 0.9.38
*/
ECIES_X25519(4, 32, 32, EncAlgo.ECIES, "EC/None/NoPadding", X25519_SPEC, "0.9.38");
ECIES_X25519(4, 32, 32, EncAlgo.ECIES, "EC/None/NoPadding", X25519_SPEC, "0.9.38"),
/**
* Proposal 169.
* Pubkey 32 bytes; privkey 32 bytes
* @since 0.9.67
*/
MLKEM512_X25519(5, 32, 32, EncAlgo.ECIES_MLKEM, "EC/None/NoPadding", X25519_SPEC, "0.9.67"),
/**
* Proposal 169.
* Pubkey 32 bytes; privkey 32 bytes
* @since 0.9.67
*/
MLKEM768_X25519(6, 32, 32, EncAlgo.ECIES_MLKEM, "EC/None/NoPadding", X25519_SPEC, "0.9.67"),
/**
* Proposal 169.
* Pubkey 32 bytes; privkey 32 bytes
* @since 0.9.67
*/
MLKEM1024_X25519(7, 32, 32, EncAlgo.ECIES_MLKEM, "EC/None/NoPadding", X25519_SPEC, "0.9.67"),
/**
* For internal use only (Alice side)
* Proposal 169.
* Pubkey 800 bytes; privkey 1632 bytes
* @since 0.9.67
*/
MLKEM512_X25519_INT(100005, 800, 1632, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.67"),
/**
* For internal use only (Alice side)
* Proposal 169.
* Pubkey 1184 bytes; privkey 2400 bytes
* @since 0.9.67
*/
MLKEM768_X25519_INT(100006, 1184, 2400, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.67"),
/**
* For internal use only (Alice side)
* Proposal 169.
* Pubkey 1568 bytes; privkey 3168 bytes
* @since 0.9.67
*/
MLKEM1024_X25519_INT(100007, 1568, 3168, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.67"),
/**
* For internal use only (Bob side ciphertext)
* Proposal 169.
* Pubkey 768 bytes; privkey 0
* @since 0.9.67
*/
MLKEM512_X25519_CT(100008, 768, 0, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.67"),
/**
* For internal use only (Bob side ciphertext)
* Proposal 169.
* Pubkey 1088 bytes; privkey 0
* @since 0.9.67
*/
MLKEM768_X25519_CT(100009, 1088, 0, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.67"),
/**
* For internal use only (Bob side ciphertext)
* Proposal 169.
* Pubkey 1568 bytes; privkey 0
* @since 0.9.67
*/
MLKEM1024_X25519_CT(100010, 1568, 0, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.67");
private final int code, pubkeyLen, privkeyLen;
private final EncAlgo base;
private final String algoName, since;
private final AlgorithmParameterSpec params;
private final boolean isAvail;
private final boolean isAvail, isPQ;
/**
*
@ -68,7 +137,7 @@ public enum EncType {
*/
EncType(int cod, int pubLen, int privLen, EncAlgo baseAlgo,
String transformation, AlgorithmParameterSpec pSpec, String supportedSince) {
if (pubLen > 256)
if (pubLen > 256 && baseAlgo != EncAlgo.ECIES_MLKEM_INT)
throw new IllegalArgumentException("fixup PublicKey for longer keys");
code = cod;
pubkeyLen = pubLen;
@ -78,6 +147,7 @@ public enum EncType {
params = pSpec;
since = supportedSince;
isAvail = x_isAvailable();
isPQ = base == EncAlgo.ECIES_MLKEM;
}
/** the unique identifier for this type */
@ -120,11 +190,16 @@ public enum EncType {
}
private boolean x_isAvailable() {
if (ELGAMAL_2048 == this)
return true;
// EC types are placeholders for now
if (base == EncAlgo.EC)
return false;
switch (base) {
case ELGAMAL:
return true;
// EC types are placeholders for now
case EC:
// internal types
case ECIES_MLKEM_INT:
return false;
}
try {
getParams();
} catch (InvalidParameterSpecException e) {
@ -154,6 +229,14 @@ public enum EncType {
return type.isAvailable();
}
/**
* @since 0.9.67
* @return true if this is a PQ type
*/
public boolean isPQ() {
return isPQ;
}
private static final EncType[] BY_CODE;
static {

View File

@ -196,6 +196,9 @@ public final class KeyGenerator {
break;
case ECIES_X25519:
case MLKEM512_X25519:
case MLKEM768_X25519:
case MLKEM1024_X25519:
byte[] bpriv = new byte[32];
do {
_context.random().nextBytes(bpriv);
@ -238,6 +241,9 @@ public final class KeyGenerator {
break;
case ECIES_X25519:
case MLKEM512_X25519:
case MLKEM768_X25519:
case MLKEM1024_X25519:
data = new byte[32];
Curve25519.eval(data, 0, priv.getData(), null);
break;
@ -342,6 +348,7 @@ public final class KeyGenerator {
}
java.security.PublicKey pubkey = kp.getPublic();
java.security.PrivateKey privkey = kp.getPrivate();
SimpleDataStructure[] keys = new SimpleDataStructure[2];
keys[0] = SigUtil.fromJavaKey(pubkey, type);
keys[1] = SigUtil.fromJavaKey(privkey, type);
@ -476,6 +483,7 @@ public final class KeyGenerator {
System.out.println(type + " private-to-public test PASSED");
else
System.out.println(type + " private-to-public test FAILED");
//System.out.println("privkey " + keys[1]);
MessageDigest md = type.getDigestInstance();
for (int i = 0; i < runs; i++) {

View File

@ -0,0 +1,21 @@
/**
* The Bouncy Castle License
*
* Copyright (c) 2000-2023 The Legion Of The Bouncy Castle Inc. (https://www.bouncycastle.org)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View File

@ -39,12 +39,15 @@ public class HandshakeState implements Destroyable, Cloneable {
private final boolean isInitiator;
private DHState localKeyPair;
private DHState localEphemeral;
private DHState localHybrid;
private DHState remotePublicKey;
private DHState remoteEphemeral;
private DHState remoteHybrid;
private int action;
private final int requirements;
private int patternIndex;
private boolean wasCloned;
private boolean isDestroyed;
/**
* Enumerated value that indicates that the handshake object
@ -139,6 +142,14 @@ public class HandshakeState implements Destroyable, Cloneable {
public static final String protocolName3 = "Noise_N_25519_ChaChaPoly_SHA256";
/** SSU2 */
public static final String protocolName4 = "Noise_XKchaobfse+hs1+hs2+hs3_25519_ChaChaPoly_SHA256";
/**
* Hybrid Ratchet
* @since 0.9.67
*/
public static final String protocolName5 = "Noise_IKhfselg2_25519+MLKEM512_ChaChaPoly_SHA256";
public static final String protocolName6 = "Noise_IKhfselg2_25519+MLKEM768_ChaChaPoly_SHA256";
public static final String protocolName7 = "Noise_IKhfselg2_25519+MLKEM1024_ChaChaPoly_SHA256";
private static final String prefix;
private final String patternId;
/** NTCP2 */
@ -151,13 +162,24 @@ public class HandshakeState implements Destroyable, Cloneable {
public static final String PATTERN_ID_N_NO_RESPONSE = "N!";
/** SSU2 */
public static final String PATTERN_ID_XK_SSU2 = "XK-SSU2";
private static String dh;
/** Hybrid Base */
private static final String PATTERN_ID_IKHFS = "IKhfs";
/**
* Hybrid Ratchet
* @since 0.9.67
*/
public static final String PATTERN_ID_IKHFS_512 = "IKhfs512";
public static final String PATTERN_ID_IKHFS_768 = "IKhfs768";
public static final String PATTERN_ID_IKHFS_1024 = "IKhfs1024";
private static final String dh;
private static final String cipher;
private static final String hash;
private final short[] pattern;
private static final short[] PATTERN_XK;
private static final short[] PATTERN_IK;
private static final short[] PATTERN_N;
private static final short[] PATTERN_IKHFS;
static {
// Parse the protocol name into its components.
@ -200,11 +222,28 @@ public class HandshakeState implements Destroyable, Cloneable {
id = components[1].substring(0, 2);
if (!PATTERN_ID_XK.equals(id))
throw new IllegalArgumentException();
// IK Hybrid
components = protocolName5.split("_");
id = components[1].substring(0, 5);
if (!PATTERN_ID_IKHFS.equals(id))
throw new IllegalArgumentException();
PATTERN_IKHFS = Pattern.lookup(id);
if (PATTERN_IKHFS == null)
throw new IllegalArgumentException("Handshake pattern is not recognized");
components = protocolName6.split("_");
id = components[1].substring(0, 5);
if (!PATTERN_ID_IKHFS.equals(id))
throw new IllegalArgumentException();
components = protocolName7.split("_");
id = components[1].substring(0, 5);
if (!PATTERN_ID_IKHFS.equals(id))
throw new IllegalArgumentException();
}
/**
* Creates a new Noise handshake.
* Noise protocol name is hardcoded.
* Not for PQ Alice side.
*
* @param patternId XK, IK, or N
* @param role The role, HandshakeState.INITIATOR or HandshakeState.RESPONDER.
@ -217,6 +256,26 @@ public class HandshakeState implements Destroyable, Cloneable {
* that is specified in the protocolName is not supported.
*/
public HandshakeState(String patternId, int role, KeyFactory xdh) throws NoSuchAlgorithmException
{
this(patternId, role, xdh, null);
}
/**
* Creates a new Noise handshake.
* Noise protocol name is hardcoded.
*
* @param patternId XK, IK, or N
* @param role The role, HandshakeState.INITIATOR or HandshakeState.RESPONDER.
* @param xdh The key pair factory for ephemeral keys
* @param hdh The key pair factory for hybrid keys, Alice side only, or null for Bob or non-hybrid
*
* @throws IllegalArgumentException The protocolName is not
* formatted correctly, or the role is not recognized.
*
* @throws NoSuchAlgorithmException One of the cryptographic algorithms
* that is specified in the protocolName is not supported.
*/
public HandshakeState(String patternId, int role, KeyFactory xdh, KeyFactory hdh) throws NoSuchAlgorithmException
{
this.patternId = patternId;
if (patternId.equals(PATTERN_ID_XK))
@ -229,6 +288,10 @@ public class HandshakeState implements Destroyable, Cloneable {
pattern = PATTERN_N;
else if (patternId.equals(PATTERN_ID_XK_SSU2))
pattern = PATTERN_XK;
else if (patternId.equals(PATTERN_ID_IKHFS_512) ||
patternId.equals(PATTERN_ID_IKHFS_768) ||
patternId.equals(PATTERN_ID_IKHFS_1024))
pattern = PATTERN_IKHFS;
else
throw new IllegalArgumentException("Handshake pattern is not recognized");
short flags = pattern[0];
@ -256,10 +319,18 @@ public class HandshakeState implements Destroyable, Cloneable {
localKeyPair = new Curve25519DHState(xdh);
if ((flags & Pattern.FLAG_LOCAL_EPHEMERAL) != 0)
localEphemeral = new Curve25519DHState(xdh);
if ((flags & Pattern.FLAG_LOCAL_HYBRID) != 0) {
if (isInitiator && hdh == null)
throw new IllegalArgumentException("Hybrid patterns require hybrid key generator");
localHybrid = isInitiator ? new MLKEMDHState(hdh, patternId) : new MLKEMDHState(false, patternId);
}
if ((flags & Pattern.FLAG_REMOTE_STATIC) != 0)
remotePublicKey = new Curve25519DHState(xdh);
if ((flags & Pattern.FLAG_REMOTE_EPHEMERAL) != 0)
remoteEphemeral = new Curve25519DHState(xdh);
if ((flags & Pattern.FLAG_REMOTE_HYBRID) != 0) {
remoteHybrid = new MLKEMDHState(!isInitiator, patternId);
}
}
@ -294,6 +365,23 @@ public class HandshakeState implements Destroyable, Cloneable {
remotePublicKey = o.remotePublicKey.clone();
if (o.remoteEphemeral != null)
remoteEphemeral = o.remoteEphemeral.clone();
if (o.localHybrid != null) {
if (isInitiator) {
// always save Alice's local keys
localHybrid = o.localHybrid.clone();
} else {
if (o.wasCloned) {
// new keys after first time for Bob
localHybrid = o.localHybrid.clone();
} else {
// first time for Bob, use the eph. keys previously generated
localHybrid = o.localHybrid;
o.wasCloned = true;
}
}
}
if (o.remoteHybrid != null)
remoteHybrid = o.remoteHybrid.clone();
action = o.action;
if (action == SPLIT || action == COMPLETE)
throw new CloneNotSupportedException("clone after NSR");
@ -419,6 +507,32 @@ public class HandshakeState implements Destroyable, Cloneable {
return false;
}
/**
* Gets the keypair object for the local hybrid key.
*
* I2P
*
* @return The keypair, or null if a local hybrid key is not required or has not been generated.
* @since 0.9.67
*/
public DHState getLocalHybridKeyPair()
{
return localHybrid;
}
/**
* Gets the keypair object for the remote hybrid key.
*
* I2P
*
* @return The keypair, or null if a remote hybrid key is not required or has not been generated.
* @since 0.9.67
*/
public DHState getRemoteHybridKeyPair()
{
return remoteHybrid;
}
// Empty value for when the prologue is not supplied.
private static final byte[] emptyPrologue = new byte [0];
@ -472,17 +586,11 @@ public class HandshakeState implements Destroyable, Cloneable {
if (isInitiator) {
if ((requirements & LOCAL_PREMSG) != 0)
symmetric.mixPublicKey(localKeyPair);
if ((requirements & FALLBACK_PREMSG) != 0) {
symmetric.mixPublicKey(remoteEphemeral);
}
if ((requirements & REMOTE_PREMSG) != 0)
symmetric.mixPublicKey(remotePublicKey);
} else {
if ((requirements & REMOTE_PREMSG) != 0)
symmetric.mixPublicKey(remotePublicKey);
if ((requirements & FALLBACK_PREMSG) != 0) {
symmetric.mixPublicKey(localEphemeral);
}
if ((requirements & LOCAL_PREMSG) != 0)
symmetric.mixPublicKey(localKeyPair);
}
@ -675,6 +783,54 @@ public class HandshakeState implements Destroyable, Cloneable {
}
break;
case Pattern.F:
{
// Generate a local hybrid keypair and add the public
// key to the message. If we are running fixed vector tests,
// then a fixed hybrid key may have already been provided.
if (localHybrid == null)
throw new IllegalStateException("Pattern definition error");
byte[] shared = null;
if (isInitiator) {
// Only Alice generates a keypair
localHybrid.generateKeyPair();
} else {
// Only Bob. We have to do the FF part here,
// so we split up mixDH()
// and do the localHybrid.calculate() first
// and the mixKey() after.
// mixDH(localHybrid, remoteHybrid)
// First part
len = localHybrid.getSharedKeyLength();
shared = new byte [len];
// this creates the ciphertext and puts it in localHybrid.publicKey
// IllegalArgumentException will be thrown here on bad remote key
localHybrid.calculate(shared, 0, remoteHybrid);
}
len = localHybrid.getPublicKeyLength();
macLen = symmetric.getMACLength();
if (space < (len + macLen))
throw new ShortBufferException();
localHybrid.getPublicKey(message, messagePosn);
messagePosn += symmetric.encryptAndHash(message, messagePosn, message, messagePosn, len);
if (!isInitiator) {
// Second part
// We do the rest of the FF part here while we have the shared key
symmetric.mixKey(shared, 0, shared.length);
Noise.destroy(shared);
}
}
break;
case Pattern.FF:
{
// DH operation with initiator and responder hybrid keys.
// We are Bob.
// This is a NOOP, we did the mixDH() in Pattern.F above.
}
break;
default:
{
// Unknown token code. Abort.
@ -857,6 +1013,35 @@ public class HandshakeState implements Destroyable, Cloneable {
mixDH(localKeyPair, remotePublicKey);
}
break;
case Pattern.F:
{
// Decrypt and read the remote hybrid ephemeral key.
if (remoteHybrid == null)
throw new IllegalStateException("Pattern definition error");
len = remoteHybrid.getPublicKeyLength();
macLen = symmetric.getMACLength();
if (space < (len + macLen))
throw new ShortBufferException();
byte[] temp = new byte [len];
try {
if (symmetric.decryptAndHash(message, messageOffset, temp, 0, len + macLen) != len)
throw new ShortBufferException();
remoteHybrid.setPublicKey(temp, 0);
} finally {
Noise.destroy(temp);
}
messageOffset += len + macLen;
}
break;
case Pattern.FF:
{
// DH operation with initiator and responder hybrid keys.
// We are Alice.
mixDH(localHybrid, remoteHybrid);
}
break;
default:
{
@ -953,17 +1138,22 @@ public class HandshakeState implements Destroyable, Cloneable {
}
@Override
public void destroy() {
public synchronized void destroy() {
isDestroyed = true;
if (symmetric != null)
symmetric.destroy();
if (localKeyPair != null)
localKeyPair.destroy();
if (localEphemeral != null)
localEphemeral.destroy();
if (localHybrid != null)
localHybrid.destroy();
if (remotePublicKey != null)
remotePublicKey.destroy();
if (remoteEphemeral != null)
remoteEphemeral.destroy();
if (remoteHybrid != null)
remoteHybrid.destroy();
}
/**
@ -992,11 +1182,6 @@ public class HandshakeState implements Destroyable, Cloneable {
requirements |= REMOTE_REQUIRED;
requirements |= REMOTE_PREMSG;
}
if ((flags & (Pattern.FLAG_REMOTE_EPHEM_REQ |
Pattern.FLAG_LOCAL_EPHEM_REQ)) != 0) {
if (isFallback)
requirements |= FALLBACK_PREMSG;
}
return requirements;
}
@ -1022,6 +1207,8 @@ public class HandshakeState implements Destroyable, Cloneable {
*/
@Override
public synchronized HandshakeState clone() throws CloneNotSupportedException {
if (isDestroyed)
throw new IllegalStateException("destroyed");
return new HandshakeState(this);
}
@ -1085,6 +1272,34 @@ public class HandshakeState implements Destroyable, Cloneable {
}
buf.append('\n');
dh = localHybrid;
if (dh != null) {
buf.append("Local hybrid public key (e1/ekem1) : ");
if (dh != null && dh.hasPublicKey()) {
tmp = new byte[dh.getPublicKeyLength()];
dh.getPublicKey(tmp, 0);
buf.append(tmp.length).append(" bytes ");
buf.append(net.i2p.data.Base64.encode(tmp));
} else {
buf.append("null");
}
buf.append('\n');
}
dh = remoteHybrid;
if (dh != null) {
buf.append("Remote hybrid public key (e1/ekem1) : ");
if (dh != null && dh.hasPublicKey()) {
tmp = new byte[dh.getPublicKeyLength()];
dh.getPublicKey(tmp, 0);
buf.append(tmp.length).append(" bytes ");
buf.append(net.i2p.data.Base64.encode(tmp));
} else {
buf.append("null");
}
buf.append('\n');
}
return buf.toString();
}
}

View File

@ -0,0 +1,275 @@
/*
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
package com.southernstorm.noise.protocol;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import net.i2p.crypto.KeyFactory;
import net.i2p.crypto.KeyPair;
import net.i2p.crypto.EncType;
import net.i2p.router.crypto.pqc.MLKEM;
/**
* Implementation of the MLKEM algorithm for the Noise protocol.
*
* @since 0.9.67
*/
class MLKEMDHState implements DHState, Cloneable {
private final EncType type;
private final byte[] publicKey;
private final byte[] privateKey;
private int mode;
private final KeyFactory _hdh;
/**
* Bob local/remote or Alice remote side, do not call generateKeyPair()
* @param isAlice true for Bob remote side, false for Bob local side and Alice remote side
*/
public MLKEMDHState(boolean isAlice, String patternId)
{
this(isAlice, null, patternId);
}
/**
* Alice local side
*/
public MLKEMDHState(KeyFactory hdh, String patternId)
{
this(true, hdh, patternId);
}
/**
* Internal
*/
private MLKEMDHState(boolean isAlice, KeyFactory hdh, String patternId)
{
if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_512)) {
type = isAlice ? EncType.MLKEM512_X25519_INT : EncType.MLKEM512_X25519_CT;
} else if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_768)) {
type = isAlice ? EncType.MLKEM768_X25519_INT : EncType.MLKEM768_X25519_CT;
} else if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_1024)) {
type = isAlice ? EncType.MLKEM1024_X25519_INT : EncType.MLKEM1024_X25519_CT;
} else {
throw new IllegalArgumentException("Handshake pattern is not recognized");
}
publicKey = new byte [type.getPubkeyLen()];
privateKey = isAlice ? new byte [type.getPrivkeyLen()] : null;
mode = 0;
_hdh = hdh;
}
@Override
public void destroy() {
clearKey();
}
@Override
public String getDHName() {
return "MLKEM";
}
/**
* Note: Alice/Bob sizes are different
*/
@Override
public int getPublicKeyLength() {
return type.getPubkeyLen();
}
/**
* Note: Alice/Bob sizes are different
* @return 0 for Bob
* @deprecated
*/
@Deprecated
@Override
public int getPrivateKeyLength() {
return type.getPrivkeyLen();
}
@Override
public int getSharedKeyLength() {
return 32;
}
/**
* Alice local side ONLY
*/
@Override
public void generateKeyPair() {
if (_hdh == null)
throw new IllegalStateException("Don't keygen PQ on Bob side");
KeyPair kp = _hdh.getKeys();
System.arraycopy(kp.getPrivate().getData(), 0, privateKey, 0, type.getPrivkeyLen());
System.arraycopy(kp.getPublic().getData(), 0, publicKey, 0, type.getPubkeyLen());
mode = 0x03;
}
@Override
public void getPublicKey(byte[] key, int offset) {
System.arraycopy(publicKey, 0, key, offset, type.getPubkeyLen());
}
@Override
public void setPublicKey(byte[] key, int offset) {
System.arraycopy(key, offset, publicKey, 0, type.getPubkeyLen());
if (privateKey != null)
Arrays.fill(privateKey, (byte)0);
mode = 0x01;
}
/**
* @deprecated
*/
@Deprecated
@Override
public void getPrivateKey(byte[] key, int offset) {
throw new UnsupportedOperationException();
}
/**
* @deprecated
*/
@Deprecated
@Override
public void setPrivateKey(byte[] key, int offset) {
throw new UnsupportedOperationException();
}
/**
* @deprecated
*/
@Deprecated
@Override
public void setKeys(byte[] privkey, int privoffset, byte[] pubkey, int puboffset) {
throw new UnsupportedOperationException();
}
@Override
public void setToNullPublicKey() {
Arrays.fill(publicKey, (byte)0);
if (privateKey != null)
Arrays.fill(privateKey, (byte)0);
mode = 0x01;
}
@Override
public void clearKey() {
Noise.destroy(publicKey);
if (privateKey != null)
Noise.destroy(privateKey);
mode = 0;
}
@Override
public boolean hasPublicKey() {
return (mode & 0x01) != 0;
}
@Override
public boolean hasPrivateKey() {
return (mode & 0x02) != 0;
}
@Override
public boolean isNullPublicKey() {
if ((mode & 0x01) == 0)
return false;
int temp = 0;
for (int index = 0; index < publicKey.length; ++index)
temp |= publicKey[index];
return temp == 0;
}
/**
* I2P
*/
@Override
public boolean hasEncodedPublicKey() {
return false;
}
/**
* @deprecated
*/
@Deprecated
@Override
public void getEncodedPublicKey(byte[] key, int offset) {
throw new UnsupportedOperationException();
}
/**
* Side effect: If we are Bob, copies the ciphertext to our public key
* so it may be written out in the message.
*
* @throws IllegalArgumentException on bad public key modulus
*/
@Override
public void calculate(byte[] sharedKey, int offset, DHState publicDH) {
if (!(publicDH instanceof MLKEMDHState))
throw new IllegalArgumentException("Incompatible DH algorithms");
try {
if (hasPrivateKey()) {
// we are Alice
byte[] sk = MLKEM.decaps(type, ((MLKEMDHState)publicDH).publicKey, privateKey);
System.arraycopy(sk, 0, sharedKey, offset, sk.length);
} else if (!hasPublicKey()) {
// we are Bob
byte[][] rv = MLKEM.encaps(type, ((MLKEMDHState)publicDH).publicKey);
byte[] ct = rv[0];
byte[] sk = rv[1];
System.arraycopy(sk, 0, sharedKey, offset, sk.length);
setPublicKey(ct, 0);
} else {
throw new IllegalStateException();
}
System.out.println("Calculated shared PQ key: " + net.i2p.data.Base64.encode(sharedKey, offset, 32));
} catch (GeneralSecurityException gse) {
throw new IllegalArgumentException(gse);
}
}
@Override
public void copyFrom(DHState other) {
if (!(other instanceof MLKEMDHState))
throw new IllegalStateException("Mismatched DH key objects");
if (other == this)
return;
MLKEMDHState dh = (MLKEMDHState)other;
if (dh.privateKey != null)
System.arraycopy(dh.privateKey, 0, privateKey, 0, type.getPrivkeyLen());
if (dh.publicKey != null)
System.arraycopy(dh.publicKey, 0, publicKey, 0, type.getPubkeyLen());
mode = dh.mode;
}
/**
* I2P
*/
@Override
public MLKEMDHState clone() throws CloneNotSupportedException {
return (MLKEMDHState) super.clone();
}
}

View File

@ -97,6 +97,31 @@ class Pattern {
SE
};
/**
* @since 0.9.67
*/
private static final short[] noise_pattern_IKhfs = {
FLAG_LOCAL_STATIC |
FLAG_LOCAL_EPHEMERAL |
FLAG_LOCAL_HYBRID |
FLAG_REMOTE_STATIC |
FLAG_REMOTE_EPHEMERAL |
FLAG_REMOTE_HYBRID |
FLAG_REMOTE_REQUIRED,
E,
ES,
F,
S,
SS,
FLIP_DIR,
E,
EE,
F,
FF,
SE
};
/**
* Look up the description information for a pattern.
*
@ -111,6 +136,8 @@ class Pattern {
return noise_pattern_XK;
else if (name.equals("IK"))
return noise_pattern_IK;
else if (name.equals("IKhfs"))
return noise_pattern_IKhfs;
return null;
}

View File

@ -41,17 +41,26 @@ class SymmetricState implements Destroyable, Cloneable {
private static final byte[] INIT_CK_IK;
private static final byte[] INIT_CK_N;
private static final byte[] INIT_CK_XK_SSU2;
private static final byte[] INIT_CK_IKHFS_512;
private static final byte[] INIT_CK_IKHFS_768;
private static final byte[] INIT_CK_IKHFS_1024;
// precalculated hash of the hash of the Noise name = mixHash(nullPrologue)
private static final byte[] INIT_HASH_XK = new byte[32];
private static final byte[] INIT_HASH_IK = new byte[32];
private static final byte[] INIT_HASH_N = new byte[32];
private static final byte[] INIT_HASH_XK_SSU2 = new byte[32];
private static final byte[] INIT_HASH_IKHFS_512 = new byte[32];
private static final byte[] INIT_HASH_IKHFS_768 = new byte[32];
private static final byte[] INIT_HASH_IKHFS_1024 = new byte[32];
static {
INIT_CK_XK = initHash(HandshakeState.protocolName);
INIT_CK_IK = initHash(HandshakeState.protocolName2);
INIT_CK_N = initHash(HandshakeState.protocolName3);
INIT_CK_XK_SSU2 = initHash(HandshakeState.protocolName4);
INIT_CK_IKHFS_512 = initHash(HandshakeState.protocolName5);
INIT_CK_IKHFS_768 = initHash(HandshakeState.protocolName6);
INIT_CK_IKHFS_1024 = initHash(HandshakeState.protocolName7);
try {
MessageDigest md = Noise.createHash("SHA256");
md.update(INIT_CK_XK, 0, 32);
@ -62,6 +71,12 @@ class SymmetricState implements Destroyable, Cloneable {
md.digest(INIT_HASH_N, 0, 32);
md.update(INIT_CK_XK_SSU2, 0, 32);
md.digest(INIT_HASH_XK_SSU2, 0, 32);
md.update(INIT_CK_IKHFS_512, 0, 32);
md.digest(INIT_HASH_IKHFS_512, 0, 32);
md.update(INIT_CK_IKHFS_768, 0, 32);
md.digest(INIT_HASH_IKHFS_768, 0, 32);
md.update(INIT_CK_IKHFS_1024, 0, 32);
md.digest(INIT_HASH_IKHFS_1024, 0, 32);
Noise.releaseHash(md);
} catch (Exception e) {
throw new IllegalStateException(e);
@ -136,6 +151,15 @@ class SymmetricState implements Destroyable, Cloneable {
} else if (patternId.equals(HandshakeState.PATTERN_ID_XK_SSU2)) {
initCK = INIT_CK_XK_SSU2;
initHash = INIT_HASH_XK_SSU2;
} else if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_512)) {
initCK = INIT_CK_IKHFS_512;
initHash = INIT_HASH_IKHFS_512;
} else if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_768)) {
initCK = INIT_CK_IKHFS_768;
initHash = INIT_HASH_IKHFS_768;
} else if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_1024)) {
initCK = INIT_CK_IKHFS_1024;
initHash = INIT_HASH_IKHFS_1024;
} else {
throw new IllegalArgumentException("Handshake pattern is not recognized");
}
@ -319,6 +343,7 @@ class SymmetricState implements Destroyable, Cloneable {
*/
public int decryptAndHash(byte[] ciphertext, int ciphertextOffset, byte[] plaintext, int plaintextOffset, int length) throws ShortBufferException, BadPaddingException
{
// NOTE: This updates the hash, even on failure
System.arraycopy(h, 0, prev_h, 0, h.length);
mixHash(ciphertext, ciphertextOffset, length);
return cipher.decryptWithAd(prev_h, ciphertext, ciphertextOffset, plaintext, plaintextOffset, length);

View File

@ -26,6 +26,7 @@ public class LeaseSetKeys {
private final SigningPrivateKey _revocationKey;
private final PrivateKey _decryptionKey;
private final PrivateKey _decryptionKeyEC;
private final PrivateKey _decryptionKeyPQ;
/**
* Unmodifiable, ElGamal only
@ -43,6 +44,41 @@ public class LeaseSetKeys {
*/
public static final Set<EncType> SET_BOTH = Collections.unmodifiableSet(EnumSet.of(EncType.ELGAMAL_2048, EncType.ECIES_X25519));
private static final Set<EncType> SET_NONE = Collections.emptySet();
/**
* Unmodifiable, PQ only
* @since public since 0.9.67
*/
public static final Set<EncType> SET_PQ1 = Collections.unmodifiableSet(EnumSet.of(EncType.MLKEM512_X25519));
/**
* Unmodifiable, PQ only
* @since public since 0.9.67
*/
public static final Set<EncType> SET_PQ2 = Collections.unmodifiableSet(EnumSet.of(EncType.MLKEM768_X25519));
/**
* Unmodifiable, PQ only
* @since public since 0.9.67
*/
public static final Set<EncType> SET_PQ3 = Collections.unmodifiableSet(EnumSet.of(EncType.MLKEM1024_X25519));
/**
* Unmodifiable, ECIES-X25519 and PQ only
* @since public since 0.9.67
*/
public static final Set<EncType> SET_EC_PQ1 = Collections.unmodifiableSet(EnumSet.of(EncType.ECIES_X25519, EncType.MLKEM512_X25519));
/**
* Unmodifiable, ECIES-X25519 and PQ only
* @since public since 0.9.67
*/
public static final Set<EncType> SET_EC_PQ2 = Collections.unmodifiableSet(EnumSet.of(EncType.ECIES_X25519, EncType.MLKEM768_X25519));
/**
* Unmodifiable, ECIES-X25519 and PQ only
* @since public since 0.9.67
*/
public static final Set<EncType> SET_EC_PQ3 = Collections.unmodifiableSet(EnumSet.of(EncType.ECIES_X25519, EncType.MLKEM1024_X25519));
/**
* Unmodifiable, ECIES-X25519 and PQ only
* @since public since 0.9.67
*/
public static final Set<EncType> SET_EC_PQ_ALL = Collections.unmodifiableSet(EnumSet.of(EncType.ECIES_X25519, EncType.MLKEM512_X25519, EncType.MLKEM768_X25519, EncType.MLKEM1024_X25519));
/**
* Client with a single key
@ -57,9 +93,15 @@ public class LeaseSetKeys {
if (type == EncType.ELGAMAL_2048) {
_decryptionKey = decryptionKey;
_decryptionKeyEC = null;
_decryptionKeyPQ = null;
} else if (type == EncType.ECIES_X25519) {
_decryptionKey = null;
_decryptionKeyEC = decryptionKey;
_decryptionKeyPQ = null;
} else if (type.isPQ()) {
_decryptionKey = null;
_decryptionKeyEC =null;
_decryptionKeyPQ = decryptionKey;
} else {
throw new IllegalArgumentException("Unknown type " + type);
}
@ -68,9 +110,13 @@ public class LeaseSetKeys {
/**
* Client with multiple keys
*
* The ONLY valid combinations are X25519 + ElG or X25519 + (MLKEM512 OR MLKEM768 OR MLKEM1024).
* Other combinations will throw IllegalArgumentException.
*
* @param dest unused
* @param revocationKey unused, may be null
* @param decryptionKeys non-null, non-empty
* @throws IllegalArgumentException
* @since 0.9.44
*/
public LeaseSetKeys(Destination dest, SigningPrivateKey revocationKey, List<PrivateKey> decryptionKeys) {
@ -79,22 +125,32 @@ public class LeaseSetKeys {
_revocationKey = revocationKey;
PrivateKey elg = null;
PrivateKey ec = null;
PrivateKey pq = null;
for (PrivateKey pk : decryptionKeys) {
EncType type = pk.getType();
if (type == EncType.ELGAMAL_2048) {
if (elg != null)
throw new IllegalArgumentException("Multiple keys same type");
if (pq != null)
throw new IllegalArgumentException("Invalid combination ElG + PQ");
elg = pk;
} else if (type == EncType.ECIES_X25519) {
if (ec != null)
throw new IllegalArgumentException("Multiple keys same type");
ec = pk;
} else if (type.isPQ()) {
if (pq != null)
throw new IllegalArgumentException("Multiple keys same type");
if (elg != null)
throw new IllegalArgumentException("Invalid combination ElG + PQ");
pq = pk;
} else {
throw new IllegalArgumentException("Unknown type " + type);
}
}
_decryptionKey = elg;
_decryptionKeyEC = ec;
_decryptionKeyPQ = pq;
}
/**
@ -128,9 +184,17 @@ public class LeaseSetKeys {
return _decryptionKey;
if (type == EncType.ECIES_X25519)
return _decryptionKeyEC;
if (type.isPQ() && _decryptionKeyPQ != null && _decryptionKeyPQ.getType() == type)
return _decryptionKeyPQ;
return null;
}
/**
* @return PQ key (any type) or null if the LS does not support PQ
* @since 0.9.67
*/
public PrivateKey getPQDecryptionKey() { return _decryptionKeyPQ; }
/**
* Do we support this type of encryption?
*
@ -141,6 +205,8 @@ public class LeaseSetKeys {
return _decryptionKey != null;
if (type == EncType.ECIES_X25519)
return _decryptionKeyEC != null;
if (type.isPQ())
return _decryptionKeyPQ != null && _decryptionKeyPQ.getType() == type;
return false;
}
@ -152,6 +218,27 @@ public class LeaseSetKeys {
public Set<EncType> getSupportedEncryption() {
if (_decryptionKey != null)
return (_decryptionKeyEC != null) ? SET_BOTH : SET_ELG;
if (_decryptionKeyPQ != null) {
if (_decryptionKeyEC != null) {
switch (_decryptionKeyPQ.getType()) {
case MLKEM512_X25519:
return SET_EC_PQ1;
case MLKEM768_X25519:
return SET_EC_PQ2;
case MLKEM1024_X25519:
return SET_EC_PQ3;
}
} else {
switch (_decryptionKeyPQ.getType()) {
case MLKEM512_X25519:
return SET_PQ1;
case MLKEM768_X25519:
return SET_PQ2;
case MLKEM1024_X25519:
return SET_PQ3;
}
}
}
return (_decryptionKeyEC != null) ? SET_EC : SET_NONE;
}
}

View File

@ -26,6 +26,7 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import net.i2p.client.I2PClient;
import net.i2p.crypto.EncType;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.data.DatabaseEntry;
import net.i2p.data.DataHelper;
@ -50,6 +51,7 @@ import net.i2p.router.JobImpl;
import net.i2p.router.RouterContext;
import net.i2p.router.crypto.TransientSessionKeyManager;
import net.i2p.router.crypto.ratchet.RatchetSKM;
import net.i2p.router.crypto.ratchet.MuxedPQSKM;
import net.i2p.router.crypto.ratchet.MuxedSKM;
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
import net.i2p.util.ConcurrentHashSet;
@ -605,6 +607,8 @@ class ClientConnectionRunner {
int thresh = TransientSessionKeyManager.LOW_THRESHOLD;
boolean hasElg = false;
boolean hasEC = false;
boolean hasPQ = false;
int pqType = 0;
// router may be null in unit tests, avoid NPEs in ratchet
// we won't actually be using any SKM anyway
if (opts != null && _context.router() != null) {
@ -620,10 +624,18 @@ class ClientConnectionRunner {
if (senc != null) {
String[] senca = DataHelper.split(senc, ",");
for (String sencaa : senca) {
if (sencaa.equals("0"))
if (sencaa.equals("0")) {
hasElg = true;
else if (sencaa.equals("4"))
} else if (sencaa.equals("4")) {
hasEC = true;
} else if (sencaa.equals("5") || sencaa.equals("6") || sencaa.equals("7")) {
if (hasPQ) {
_log.error("Bad encryption type combination in i2cp.leaseSetEncType for " + dest.toBase32());
return SessionStatusMessage.STATUS_INVALID;
}
pqType = Integer.parseInt(sencaa);
hasPQ = true;
}
}
} else {
hasElg = true;
@ -632,6 +644,10 @@ class ClientConnectionRunner {
hasElg = true;
}
if (hasElg) {
if (hasPQ) {
_log.error("Bad encryption type combination in i2cp.leaseSetEncType for " + dest.toBase32());
return SessionStatusMessage.STATUS_INVALID;
}
TransientSessionKeyManager tskm = new TransientSessionKeyManager(_context, tags, thresh);
if (hasEC) {
RatchetSKM rskm = new RatchetSKM(_context, dest);
@ -639,6 +655,16 @@ class ClientConnectionRunner {
} else {
_sessionKeyManager = tskm;
}
} else if (hasPQ) {
if (hasEC) {
// ECIES
RatchetSKM rskm1 = new RatchetSKM(_context, dest);
// PQ
RatchetSKM rskm2 = new RatchetSKM(_context, dest, EncType.getByCode(pqType));
_sessionKeyManager = new MuxedPQSKM(rskm1, rskm2);
} else {
_sessionKeyManager = new RatchetSKM(_context, dest, EncType.getByCode(pqType));
}
} else {
if (hasEC) {
_sessionKeyManager = new RatchetSKM(_context, dest);

View File

@ -16,6 +16,7 @@ import com.southernstorm.noise.protocol.HandshakeState;
import net.i2p.crypto.EncType;
import net.i2p.crypto.HKDF;
import net.i2p.crypto.KeyFactory;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.data.Base64;
import net.i2p.data.Certificate;
@ -33,6 +34,7 @@ import net.i2p.data.SessionTag;
import net.i2p.data.i2np.DatabaseStoreMessage;
import net.i2p.data.i2np.GarlicClove;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.crypto.pqc.MLKEM;
import static net.i2p.router.crypto.ratchet.RatchetPayload.*;
import net.i2p.router.LeaseSetKeys;
import net.i2p.router.Router;
@ -53,6 +55,7 @@ public final class ECIESAEADEngine {
private final RouterContext _context;
private final Log _log;
private final MuxedEngine _muxedEngine;
private final MuxedPQEngine _muxedPQEngine;
private final HKDF _hkdf;
private final Elg2KeyFactory _edhThread;
private boolean _isRunning;
@ -86,6 +89,24 @@ public final class ECIESAEADEngine {
private static final String INFO_0 = "SessionReplyTags";
private static final String INFO_6 = "AttachPayloadKDF";
// These are the min sizes for the MLKEM New Session Message.
// It contains an extra MLKEM key and MAC.
// 112
private static final int NS_MLKEM_OVERHEAD = NS_OVERHEAD + MACLEN;
// 800 + 112 + 7 = 919
private static final int MIN_NS_MLKEM512_SIZE = EncType.MLKEM512_X25519_INT.getPubkeyLen() + NS_MLKEM_OVERHEAD + DATETIME_SIZE;
// 1184 + 112 + 7 = 1303
private static final int MIN_NS_MLKEM768_SIZE = EncType.MLKEM768_X25519_INT.getPubkeyLen() + NS_MLKEM_OVERHEAD + DATETIME_SIZE;
// 1568 + 112 + 7 = 1687
private static final int MIN_NS_MLKEM1024_SIZE = EncType.MLKEM1024_X25519_INT.getPubkeyLen() + NS_MLKEM_OVERHEAD + DATETIME_SIZE;
// 856
private static final int MIN_NSR_MLKEM512_SIZE = EncType.MLKEM512_X25519_CT.getPubkeyLen() + MIN_NSR_SIZE + MACLEN;
// 1176
private static final int MIN_NSR_MLKEM768_SIZE = EncType.MLKEM768_X25519_CT.getPubkeyLen() + MIN_NSR_SIZE + MACLEN;
// 1656
private static final int MIN_NSR_MLKEM1024_SIZE = EncType.MLKEM1024_X25519_CT.getPubkeyLen() + MIN_NSR_SIZE + MACLEN;
/**
* Caller MUST call startup() to get threaded generation.
* Will still work without, will just generate inline.
@ -96,6 +117,7 @@ public final class ECIESAEADEngine {
_context = ctx;
_log = _context.logManager().getLog(ECIESAEADEngine.class);
_muxedEngine = new MuxedEngine(ctx);
_muxedPQEngine = new MuxedPQEngine(ctx);
_hkdf = new HKDF(ctx);
_edhThread = new Elg2KeyFactory(ctx);
@ -147,6 +169,18 @@ public final class ECIESAEADEngine {
return _muxedEngine.decrypt(data, elgKey, ecKey, keyManager);
}
/**
* Try to decrypt the message with one or both of the given private keys
*
* @param ecKey must be EC, non-null
* @param pqKey must be PQ, non-null
* @return decrypted data or null on failure
* @since 0.9.67
*/
public CloveSet decrypt(byte data[], PrivateKey ecKey, PrivateKey pqKey, MuxedPQSKM keyManager) throws DataFormatException {
return _muxedPQEngine.decrypt(data, ecKey, pqKey, keyManager);
}
/**
* Decrypt the message using the given private key
* and using tags from the specified key manager.
@ -175,8 +209,7 @@ public final class ECIESAEADEngine {
private CloveSet x_decrypt(byte data[], PrivateKey targetPrivateKey,
RatchetSKM keyManager) throws DataFormatException {
if (targetPrivateKey.getType() != EncType.ECIES_X25519)
throw new IllegalArgumentException();
checkType(targetPrivateKey.getType());
if (data == null) {
if (_log.shouldLog(Log.ERROR)) _log.error("Null data being decrypted?");
return null;
@ -267,14 +300,22 @@ public final class ECIESAEADEngine {
if (shouldDebug)
_log.debug("Decrypting ES with tag: " + st.toBase64() + " key: " + key + ": " + data.length + " bytes");
decrypted = decryptExistingSession(tag, data, key, targetPrivateKey, keyManager);
} else if (data.length >= MIN_NSR_SIZE) {
if (shouldDebug)
_log.debug("Decrypting NSR with tag: " + st.toBase64() + " key: " + key + ": " + data.length + " bytes");
decrypted = decryptNewSessionReply(tag, data, state, keyManager);
} else {
decrypted = null;
if (_log.shouldWarn())
_log.warn("ECIES decrypt fail, tag found but no state and too small for NSR: " + data.length + " bytes");
// it's important not to attempt decryption for too-short packets,
// because Noise will destroy() the handshake state on failure,
// and we don't clone() on the first one, so it's fatal.
EncType type = targetPrivateKey.getType();
int min = getMinNSRSize(type);
if (data.length >= min) {
if (shouldDebug)
_log.debug("Decrypting NSR with tag: " + st.toBase64() + " key: " + key + ": " + data.length + " bytes");
decrypted = decryptNewSessionReply(tag, data, state, keyManager);
} else {
decrypted = null;
if (_log.shouldWarn())
_log.warn("NSR decrypt fail, tag: " + st.toBase64() + " but packet too small: " + data.length + " bytes, min is " +
min + " on state " + state);
}
}
if (decrypted != null) {
_context.statManager().updateFrequency("crypto.eciesAEAD.decryptExistingSession");
@ -316,8 +357,10 @@ public final class ECIESAEADEngine {
private CloveSet x_decryptSlow(byte data[], PrivateKey targetPrivateKey,
RatchetSKM keyManager) throws DataFormatException {
CloveSet decrypted;
EncType type = targetPrivateKey.getType();
int minns = getMinNSSize(type);
boolean isRouter = keyManager.getDestination() == null;
if (data.length >= MIN_NS_SIZE || (isRouter && data.length >= MIN_NS_N_SIZE)) {
if (data.length >= minns || (isRouter && data.length >= MIN_NS_N_SIZE)) {
if (isRouter)
decrypted = decryptNewSession_N(data, targetPrivateKey, keyManager);
else
@ -333,11 +376,116 @@ public final class ECIESAEADEngine {
} else {
decrypted = null;
if (_log.shouldDebug())
_log.debug("ECIES decrypt fail, too small for NS: " + data.length + " bytes");
_log.debug("ECIES decrypt fail, too small for NS: " + data.length + " bytes, min is " +
minns + " for type " + type);
}
return decrypted;
}
/**
* @throws IllegalArgumentException if unsupported
* @since 0.9.67
*/
private static void checkType(EncType type) {
switch(type) {
case ECIES_X25519:
case MLKEM512_X25519:
case MLKEM768_X25519:
case MLKEM1024_X25519:
return;
default:
throw new IllegalArgumentException("Unsupported key type " + type);
}
}
/**
* @since 0.9.67
*/
private static String getNoisePattern(EncType type) {
switch(type) {
case ECIES_X25519:
return HandshakeState.PATTERN_ID_IK;
case MLKEM512_X25519:
return HandshakeState.PATTERN_ID_IKHFS_512;
case MLKEM768_X25519:
return HandshakeState.PATTERN_ID_IKHFS_768;
case MLKEM1024_X25519:
return HandshakeState.PATTERN_ID_IKHFS_1024;
default:
throw new IllegalArgumentException("No pattern for " + type);
}
}
/**
* @since 0.9.67
*/
private static KeyFactory getHybridKeyFactory(EncType type) {
switch(type) {
case MLKEM512_X25519:
return MLKEM.MLKEM512KeyFactory;
case MLKEM768_X25519:
return MLKEM.MLKEM768KeyFactory;
case MLKEM1024_X25519:
return MLKEM.MLKEM1024KeyFactory;
default:
return null;
}
}
/**
* @since 0.9.67
*/
private static int getMinNSSize(EncType type) {
switch(type) {
case ECIES_X25519:
return MIN_NS_SIZE;
case MLKEM512_X25519:
return MIN_NS_MLKEM512_SIZE;
case MLKEM768_X25519:
return MIN_NS_MLKEM768_SIZE;
case MLKEM1024_X25519:
return MIN_NS_MLKEM1024_SIZE;
default:
throw new IllegalArgumentException("No pattern for " + type);
}
}
/**
* @since 0.9.67
*/
private static int getMinNSRSize(EncType type) {
switch(type) {
case ECIES_X25519:
return MIN_NSR_SIZE;
case MLKEM512_X25519:
return MIN_NSR_MLKEM512_SIZE;
case MLKEM768_X25519:
return MIN_NSR_MLKEM768_SIZE;
case MLKEM1024_X25519:
return MIN_NSR_MLKEM1024_SIZE;
default:
throw new IllegalArgumentException("No pattern for " + type);
}
}
/**
* @since 0.9.67
*/
private static Set<EncType> getEncTypeSet(EncType type) {
switch(type) {
case ECIES_X25519:
return LeaseSetKeys.SET_EC;
case MLKEM512_X25519:
return LeaseSetKeys.SET_PQ1;
case MLKEM768_X25519:
return LeaseSetKeys.SET_PQ2;
case MLKEM1024_X25519:
return LeaseSetKeys.SET_PQ3;
default:
throw new IllegalArgumentException("No pattern for " + type);
}
}
/**
* scenario 1: New Session Message
*
@ -383,8 +531,12 @@ public final class ECIESAEADEngine {
System.arraycopy(pk.getData(), 0, data, 0, KEYLEN);
HandshakeState state;
EncType type = targetPrivateKey.getType();
try {
state = new HandshakeState(HandshakeState.PATTERN_ID_IK, HandshakeState.RESPONDER, _edhThread);
String pattern = getNoisePattern(type);
// Bob does not need a key factory
//state = new HandshakeState(pattern, HandshakeState.RESPONDER, _edhThread, getHybridKeyFactory(type));
state = new HandshakeState(pattern, HandshakeState.RESPONDER, _edhThread);
} catch (GeneralSecurityException gse) {
throw new IllegalStateException("bad proto", gse);
}
@ -392,9 +544,13 @@ public final class ECIESAEADEngine {
targetPrivateKey.toPublic().getData(), 0);
state.start();
if (_log.shouldDebug())
_log.debug("State before decrypt new session: " + state);
_log.debug("State before decrypt new session (" + data.length + " bytes) " + state);
int payloadlen = data.length - (KEYLEN + KEYLEN + MACLEN + MACLEN);
DHState hyb = state.getRemoteHybridKeyPair();
if (hyb != null) {
payloadlen -= hyb.getPublicKeyLength() + MACLEN;
}
byte[] payload = new byte[payloadlen];
try {
state.readMessage(data, 0, data.length, payload, 0);
@ -402,7 +558,7 @@ public final class ECIESAEADEngine {
// we'll get this a lot on muxed SKM
// logged at INFO in caller
if (_log.shouldDebug())
_log.debug("Decrypt fail NS, state at failure: " + state, gse);
_log.debug("Decrypt fail NS " + data.length + " bytes, state at failure: " + state, gse);
// restore original data for subsequent ElG attempt
System.arraycopy(xx, 0, data, 0, KEYLEN - 1);
data[KEYLEN - 1] = xx31;
@ -467,7 +623,7 @@ public final class ECIESAEADEngine {
state.destroy();
} else {
// tell the SKM
PublicKey alice = new PublicKey(EncType.ECIES_X25519, alicePK);
PublicKey alice = new PublicKey(type, alicePK);
keyManager.createSession(alice, null, state, null);
setResponseTimerNS(alice, pc.cloveSet, keyManager);
}
@ -638,8 +794,13 @@ public final class ECIESAEADEngine {
state.mixHash(tag, 0, TAGLEN);
if (_log.shouldDebug())
_log.debug("State after mixhash tag before decrypt new session reply: " + state);
int tmplen = 48;
DHState hyb = state.getRemoteHybridKeyPair();
if (hyb != null) {
tmplen += hyb.getPublicKeyLength() + MACLEN;
}
try {
state.readMessage(data, 8, 48, ZEROLEN, 0);
state.readMessage(data, 8, tmplen, ZEROLEN, 0);
} catch (GeneralSecurityException gse) {
if (_log.shouldWarn()) {
_log.warn("Decrypt fail NSR part 1", gse);
@ -667,9 +828,16 @@ public final class ECIESAEADEngine {
byte[] encpayloadkey = new byte[32];
_hkdf.calculate(split.k_ba.getData(), ZEROLEN, INFO_6, encpayloadkey);
rcvr.initializeKey(encpayloadkey, 0);
byte[] payload = new byte[data.length - (TAGLEN + KEYLEN + MACLEN + MACLEN)];
int off = TAGLEN + KEYLEN + MACLEN;
int plen = data.length - (TAGLEN + KEYLEN + MACLEN + MACLEN);
if (hyb != null) {
int len = hyb.getPublicKeyLength() + MACLEN;
off += len;
plen -= len;
}
byte[] payload = new byte[plen];
try {
rcvr.decryptWithAd(hash, data, TAGLEN + KEYLEN + MACLEN, payload, 0, payload.length + MACLEN);
rcvr.decryptWithAd(hash, data, off, payload, 0, plen + MACLEN);
} catch (GeneralSecurityException gse) {
if (_log.shouldWarn()) {
_log.warn("Decrypt fail NSR part 2", gse);
@ -714,7 +882,7 @@ public final class ECIESAEADEngine {
}
// tell the SKM
PublicKey bob = new PublicKey(EncType.ECIES_X25519, bobPK);
PublicKey bob = new PublicKey(keyManager.getType(), bobPK);
keyManager.updateSession(bob, oldState, state, null, split);
if (pc == null)
@ -872,8 +1040,7 @@ public final class ECIESAEADEngine {
private byte[] x_encrypt(CloveSet cloves, PublicKey target, Destination to, PrivateKey priv,
RatchetSKM keyManager,
ReplyCallback callback) {
if (target.getType() != EncType.ECIES_X25519)
throw new IllegalArgumentException();
checkType(target.getType());
if (Arrays.equals(target.getData(), NULLPK)) {
if (_log.shouldWarn())
_log.warn("Zero static key target");
@ -904,6 +1071,7 @@ public final class ECIESAEADEngine {
}
if (_log.shouldDebug())
_log.debug("Encrypting as NSR to " + target + " with tag " + re.tag.toBase64());
// trash old state if this throws IAE???
return encryptNewSessionReply(cloves, target, state, re.tag, keyManager, callback);
}
byte rv[] = encryptExistingSession(cloves, target, re, callback, keyManager);
@ -943,9 +1111,13 @@ public final class ECIESAEADEngine {
private byte[] encryptNewSession(CloveSet cloves, PublicKey target, Destination to, PrivateKey priv,
RatchetSKM keyManager,
ReplyCallback callback) {
EncType type = target.getType();
if (type != priv.getType())
throw new IllegalArgumentException("Key mismatch " + target + ' ' + priv);
HandshakeState state;
try {
state = new HandshakeState(HandshakeState.PATTERN_ID_IK, HandshakeState.INITIATOR, _edhThread);
String pattern = getNoisePattern(target.getType());
state = new HandshakeState(pattern, HandshakeState.INITIATOR, _edhThread, getHybridKeyFactory(type));
} catch (GeneralSecurityException gse) {
throw new IllegalStateException("bad proto", gse);
}
@ -962,7 +1134,12 @@ public final class ECIESAEADEngine {
byte[] payload = createPayload(cloves, cloves.getExpiration(), NS_OVERHEAD);
byte[] enc = new byte[KEYLEN + KEYLEN + MACLEN + payload.length + MACLEN];
int enclen = KEYLEN + KEYLEN + MACLEN + payload.length + MACLEN;
DHState hyb = state.getLocalHybridKeyPair();
if (hyb != null) {
enclen += hyb.getPublicKeyLength() + MACLEN;
}
byte[] enc = new byte[enclen];
try {
state.writeMessage(enc, 0, payload, 0, payload.length);
} catch (GeneralSecurityException gse) {
@ -1071,7 +1248,12 @@ public final class ECIESAEADEngine {
byte[] payload = createPayload(cloves, 0, NSR_OVERHEAD);
// part 1 - tag and empty payload
byte[] enc = new byte[TAGLEN + KEYLEN + MACLEN + payload.length + MACLEN];
int enclen = TAGLEN + KEYLEN + MACLEN + payload.length + MACLEN;
DHState hyb = state.getLocalHybridKeyPair();
if (hyb != null) {
enclen += hyb.getPublicKeyLength() + MACLEN;
}
byte[] enc = new byte[enclen];
System.arraycopy(tag, 0, enc, 0, TAGLEN);
try {
state.writeMessage(enc, TAGLEN, ZEROLEN, 0, 0);
@ -1103,8 +1285,12 @@ public final class ECIESAEADEngine {
byte[] encpayloadkey = new byte[32];
_hkdf.calculate(split.k_ba.getData(), ZEROLEN, INFO_6, encpayloadkey);
sender.initializeKey(encpayloadkey, 0);
int off = TAGLEN + KEYLEN + MACLEN;
if (hyb != null) {
off += hyb.getPublicKeyLength() + MACLEN;
}
try {
sender.encryptWithAd(hash, payload, 0, enc, TAGLEN + KEYLEN + MACLEN, payload.length);
sender.encryptWithAd(hash, payload, 0, enc, off, payload.length);
} catch (GeneralSecurityException gse) {
if (_log.shouldWarn())
_log.warn("Encrypt fail NSR part 2", gse);
@ -1488,7 +1674,7 @@ public final class ECIESAEADEngine {
return;
if (!ls2.isCurrent(Router.CLOCK_FUDGE_FACTOR))
continue;
PublicKey pk = ls2.getEncryptionKey(LeaseSetKeys.SET_EC);
PublicKey pk = ls2.getEncryptionKey(getEncTypeSet(from.getType()));
if (!from.equals(pk))
continue;
if (!ls2.verifySignature())

View File

@ -0,0 +1,98 @@
package net.i2p.router.crypto.ratchet;
import net.i2p.crypto.EncAlgo;
import net.i2p.crypto.EncType;
import net.i2p.data.DataFormatException;
import net.i2p.data.PrivateKey;
import net.i2p.router.RouterContext;
import net.i2p.router.message.CloveSet;
import net.i2p.util.Log;
/**
* Both EC and PQ
*
* Handles the actual decryption using the
* supplied keys and data.
*
* @since 0.9.67
*/
final class MuxedPQEngine {
private final RouterContext _context;
private final Log _log;
public MuxedPQEngine(RouterContext ctx) {
_context = ctx;
_log = _context.logManager().getLog(MuxedPQEngine.class);
}
/**
* Decrypt the message with the given private keys
*
* @param ecKey must be EC, non-null
* @param pqKey must be PQ, non-null
* @return decrypted data or null on failure
*/
public CloveSet decrypt(byte data[], PrivateKey ecKey, PrivateKey pqKey, MuxedPQSKM keyManager) throws DataFormatException {
if (ecKey.getType() != EncType.ECIES_X25519 ||
pqKey.getType().getBaseAlgorithm() != EncAlgo.ECIES_MLKEM)
throw new IllegalArgumentException();
final boolean debug = _log.shouldDebug();
CloveSet rv = null;
// Try in-order from fastest to slowest
boolean preferRatchet = keyManager.preferRatchet();
if (preferRatchet) {
// Ratchet Tag
rv = _context.eciesEngine().decryptFast(data, ecKey, keyManager.getECSKM());
if (rv != null)
return rv;
if (debug)
_log.debug("Ratchet tag not found before PQ");
}
// PQ
// Ratchet Tag
rv = _context.eciesEngine().decryptFast(data, pqKey, keyManager.getPQSKM());
if (rv != null)
return rv;
if (debug)
_log.debug("PQ tag not found");
if (!preferRatchet) {
// Ratchet Tag
rv = _context.eciesEngine().decryptFast(data, ecKey, keyManager.getECSKM());
if (rv != null)
return rv;
if (debug)
_log.debug("Ratchet tag not found after PQ");
}
if (preferRatchet) {
// Ratchet DH
rv = _context.eciesEngine().decryptSlow(data, ecKey, keyManager.getECSKM());
boolean ok = rv != null;
keyManager.reportDecryptResult(true, ok);
if (ok)
return rv;
if (debug)
_log.debug("Ratchet NS decrypt failed before PQ");
}
// PQ DH
// Minimum size checks for the larger New Session message are in ECIESAEADEngine.x_decryptSlow().
rv = _context.eciesEngine().decryptSlow(data, pqKey, keyManager.getPQSKM());
boolean isok = rv != null;
keyManager.reportDecryptResult(false, isok);
if (isok)
return rv;
if (debug)
_log.debug("PQ NS decrypt failed");
if (!preferRatchet) {
// Ratchet DH
rv = _context.eciesEngine().decryptSlow(data, ecKey, keyManager.getECSKM());
boolean ok = rv != null;
keyManager.reportDecryptResult(true, ok);
if (!ok && debug)
_log.debug("Ratchet NS decrypt failed after PQ");
}
return rv;
}
}

View File

@ -0,0 +1,231 @@
package net.i2p.router.crypto.ratchet;
import java.io.IOException;
import java.io.Writer;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import net.i2p.I2PAppContext;
import net.i2p.crypto.EncType;
import net.i2p.crypto.TagSetHandle;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
/**
* Both EC and PQ
*
* @since 0.9.67
*/
public class MuxedPQSKM extends SessionKeyManager {
private final RatchetSKM _ec;
private final RatchetSKM _pq;
private final AtomicInteger _ecCounter = new AtomicInteger();
private final AtomicInteger _pqCounter = new AtomicInteger();
// PQ is about this much slower than EC
private static final int PQ_SLOW_FACTOR = 2;
private static final int RESTART_COUNTERS = 500;
public MuxedPQSKM(RatchetSKM ec, RatchetSKM pq) {
_ec = ec;
_pq = pq;
}
public RatchetSKM getECSKM() { return _ec; }
public RatchetSKM getPQSKM() { return _pq; }
/**
* Should we try the Ratchet slow decrypt before PQ slow decrypt?
* Adaptive test based on previous mix of traffic for this SKM,
* as reported by reportDecryptResult().
*/
boolean preferRatchet() {
int ec = _ecCounter.get();
int pq = _pqCounter.get();
if (ec > RESTART_COUNTERS / 10 &&
pq > RESTART_COUNTERS / 10 &&
ec + pq > RESTART_COUNTERS) {
_ecCounter.set(0);
_pqCounter.set(0);
return true;
}
return ec >= pq / PQ_SLOW_FACTOR;
}
/**
* Report the result of a slow decrypt attempt.
*
* @param isRatchet true for EC, false for PQ
* @param success true for successful decrypt
*/
void reportDecryptResult(boolean isRatchet, boolean success) {
if (success) {
if (isRatchet)
_ecCounter.incrementAndGet();
else
_pqCounter.incrementAndGet();
}
}
/**
* ElG only
*/
@Override
public SessionKey getCurrentKey(PublicKey target) {
return null;
}
/**
* ElG only
*/
@Override
public SessionKey getCurrentOrNewKey(PublicKey target) {
return null;
}
/**
* ElG only
*/
@Override
public void createSession(PublicKey target, SessionKey key) {
}
/**
* ElG only
*/
@Override
public SessionKey createSession(PublicKey target) {
return null;
}
/**
* ElG only
*/
@Override
public SessionTag consumeNextAvailableTag(PublicKey target, SessionKey key) {
return null;
}
/**
* EC/PQ
*/
public RatchetEntry consumeNextAvailableTag(PublicKey target) {
EncType type = target.getType();
if (type == EncType.ECIES_X25519)
return _ec.consumeNextAvailableTag(target);
else
return _pq.consumeNextAvailableTag(target);
}
@Override
public int getTagsToSend() { return 0; };
@Override
public int getLowThreshold() { return 0; };
/**
* ElG only
*/
@Override
public boolean shouldSendTags(PublicKey target, SessionKey key) {
return false;
}
/**
* ElG only
*/
@Override
public boolean shouldSendTags(PublicKey target, SessionKey key, int lowThreshold) {
return false;
}
@Override
public int getAvailableTags(PublicKey target, SessionKey key) {
EncType type = target.getType();
if (type == EncType.ECIES_X25519)
return _ec.getAvailableTags(target, key);
else
return _pq.getAvailableTags(target, key);
}
@Override
public long getAvailableTimeLeft(PublicKey target, SessionKey key) {
EncType type = target.getType();
if (type == EncType.ECIES_X25519)
return _ec.getAvailableTimeLeft(target, key);
else
return _pq.getAvailableTimeLeft(target, key);
}
/**
* ElG only
*/
@Override
public TagSetHandle tagsDelivered(PublicKey target, SessionKey key, Set<SessionTag> sessionTags) {
return null;
}
/**
* ElG only
*/
@Override
public void tagsReceived(SessionKey key, Set<SessionTag> sessionTags) {
}
/**
* ElG only
*/
@Override
public void tagsReceived(SessionKey key, Set<SessionTag> sessionTags, long expire) {
}
/**
* EC only.
* One time session
* We do not support PQ one-time sessions on MuxedPQSKM.
*
* @param expire time from now
*/
public void tagsReceived(SessionKey key, RatchetSessionTag tag, long expire) {
_ec.tagsReceived(key, tag, expire);
}
@Override
public SessionKey consumeTag(SessionTag tag) {
RatchetSessionTag rstag = new RatchetSessionTag(tag.getData());
SessionKey rv = _ec.consumeTag(rstag);
if (rv == null) {
rv = _pq.consumeTag(rstag);
}
return rv;
}
@Override
public void shutdown() {
_ec.shutdown();
_pq.shutdown();
}
@Override
public void renderStatusHTML(Writer out) throws IOException {
_ec.renderStatusHTML(out);
_pq.renderStatusHTML(out);
}
/**
* ElG only
*/
@Override
public void failTags(PublicKey target, SessionKey key, TagSetHandle ts) {
}
/**
* ElG only
*/
@Override
public void tagsAcked(PublicKey target, SessionKey key, TagSetHandle ts) {
}
}

View File

@ -54,6 +54,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
private final HKDF _hkdf;
private final DecayingHashSet _replayFilter;
private final Destination _destination;
private final EncType _type;
/**
* Let outbound session tags sit around for this long before expiring them.
@ -83,7 +84,17 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
* @since 0.9.48
*/
public RatchetSKM(RouterContext context) {
this(context, null);
this(context, null, EncType.ECIES_X25519);
}
/**
* ECIES only.
*
* @param dest null for router's SKM only
* @since 0.9.48
*/
public RatchetSKM(RouterContext context, Destination dest) {
this(context, dest, EncType.ECIES_X25519);
}
/**
@ -91,12 +102,15 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
* client manager.
*
* @param dest null for router's SKM only
* @param type the encryption type
* @since 0.9.67
*/
public RatchetSKM(RouterContext context, Destination dest) {
public RatchetSKM(RouterContext context, Destination dest, EncType type) {
super(context);
_log = context.logManager().getLog(RatchetSKM.class);
_context = context;
_destination = dest;
_type = type;
_outboundSessions = new ConcurrentHashMap<PublicKey, OutboundSession>(64);
_pendingOutboundSessions = new HashMap<PublicKey, List<OutboundSession>>(64);
_inboundTagSets = new ConcurrentHashMap<RatchetSessionTag, RatchetTagSet>(128);
@ -146,6 +160,15 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
return _destination;
}
/**
* The EncType for this SKM
*
* @since 0.9.67
*/
public EncType getType() {
return _type;
}
/** RatchetTagSet */
private Set<RatchetTagSet> getRatchetTagSets() {
synchronized (_inboundTagSets) {
@ -201,8 +224,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
*/
boolean createSession(PublicKey target, Destination d, HandshakeState state, ReplyCallback callback) {
EncType type = target.getType();
if (type != EncType.ECIES_X25519)
throw new IllegalArgumentException("Bad public key type " + type);
if (type != _type)
throw new IllegalArgumentException("Bad public key type " + type + " expected " + _type);
OutboundSession sess = new OutboundSession(target, d, null, state, callback);
boolean isInbound = state.getRole() == HandshakeState.RESPONDER;
if (isInbound) {
@ -247,8 +270,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
boolean updateSession(PublicKey target, HandshakeState oldState, HandshakeState state,
ReplyCallback callback, SplitKeys split) {
EncType type = target.getType();
if (type != EncType.ECIES_X25519)
throw new IllegalArgumentException("Bad public key type " + type);
if (type != _type)
throw new IllegalArgumentException("Bad public key type " + type + " expected " + _type);
boolean isInbound = state.getRole() == HandshakeState.RESPONDER;
if (isInbound) {
// we are Bob, NSR sent

View File

@ -32,6 +32,7 @@ import net.i2p.data.router.RouterIdentity;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.LeaseSetKeys;
import net.i2p.router.RouterContext;
import net.i2p.router.crypto.ratchet.MuxedPQSKM;
import net.i2p.router.crypto.ratchet.MuxedSKM;
import net.i2p.router.crypto.ratchet.RatchetSKM;
import net.i2p.router.crypto.ratchet.RatchetSessionTag;
@ -46,13 +47,13 @@ import net.i2p.util.Log;
public class GarlicMessageBuilder {
/**
* ELGAMAL_2048 only.
* ELGAMAL_2048 only; returns false for others
*
* @param local non-null; do not use this method for the router's SessionKeyManager
* @param minTagOverride 0 for no override, &gt; 0 to override SKM's settings
*/
static boolean needsTags(RouterContext ctx, PublicKey key, Hash local, int minTagOverride) {
if (key.getType() == EncType.ECIES_X25519)
if (LeaseSetKeys.SET_EC_PQ_ALL.contains(key.getType()))
return false;
SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(local);
if (skm == null)
@ -275,7 +276,7 @@ public class GarlicMessageBuilder {
}
/**
* ECIES_X25519 only.
* ECIES_X25519 and PQ only.
* Called by OCMJH only.
*
* @param ctx scope
@ -289,7 +290,8 @@ public class GarlicMessageBuilder {
Hash from, Destination to, SessionKeyManager skm,
ReplyCallback callback) {
PublicKey key = config.getRecipientPublicKey();
if (key.getType() != EncType.ECIES_X25519)
EncType type = key.getType();
if (!LeaseSetKeys.SET_EC_PQ_ALL.contains(type))
throw new IllegalArgumentException();
Log log = ctx.logManager().getLog(GarlicMessageBuilder.class);
GarlicMessage msg = new GarlicMessage(ctx);
@ -300,7 +302,7 @@ public class GarlicMessageBuilder {
log.warn("No LSK for " + from.toBase32());
return null;
}
PrivateKey priv = lsk.getDecryptionKey(EncType.ECIES_X25519);
PrivateKey priv = lsk.getDecryptionKey(type);
if (priv == null) {
if (log.shouldWarn())
log.warn("No key for " + from.toBase32());
@ -312,6 +314,9 @@ public class GarlicMessageBuilder {
rskm = (RatchetSKM) skm;
} else if (skm instanceof MuxedSKM) {
rskm = ((MuxedSKM) skm).getECSKM();
} else if (skm instanceof MuxedPQSKM) {
MuxedPQSKM mskm = (MuxedPQSKM) skm;
rskm = type.isPQ() ? mskm.getPQSKM() : mskm.getECSKM();
} else {
if (log.shouldWarn())
log.warn("No SKM for " + from.toBase32());
@ -338,7 +343,7 @@ public class GarlicMessageBuilder {
/**
* Encrypt from an anonymous source.
* ECIES_X25519 only.
* ECIES_X25519 only. PQ not supported.
* Called by MessageWrapper only.
*
* @param ctx scope
@ -348,7 +353,7 @@ public class GarlicMessageBuilder {
*/
public static GarlicMessage buildECIESMessage(RouterContext ctx, GarlicConfig config) {
PublicKey key = config.getRecipientPublicKey();
if (key.getType() != EncType.ECIES_X25519)
if (!LeaseSetKeys.SET_EC_PQ_ALL.contains(key.getType()))
throw new IllegalArgumentException();
Log log = ctx.logManager().getLog(GarlicMessageBuilder.class);
GarlicMessage msg = new GarlicMessage(ctx);

View File

@ -19,6 +19,7 @@ import net.i2p.data.PrivateKey;
import net.i2p.data.i2np.GarlicClove;
import net.i2p.data.i2np.GarlicMessage;
import net.i2p.router.RouterContext;
import net.i2p.router.crypto.ratchet.MuxedPQSKM;
import net.i2p.router.crypto.ratchet.MuxedSKM;
import net.i2p.router.crypto.ratchet.RatchetSKM;
import net.i2p.util.Log;
@ -44,7 +45,7 @@ public class GarlicMessageParser {
}
/**
* Supports both ELGAMAL_2048 and ECIES_X25519.
* Supports ELGAMAL_2048, ECIES_X25519, and PQ
*
* @param encryptionKey either type
* @param skm use tags from this session key manager
@ -65,6 +66,8 @@ public class GarlicMessageParser {
rskm = (RatchetSKM) skm;
} else if (skm instanceof MuxedSKM) {
rskm = ((MuxedSKM) skm).getECSKM();
} else if (skm instanceof MuxedPQSKM) {
rskm = ((MuxedPQSKM) skm).getECSKM();
} else {
if (_log.shouldWarn())
_log.warn("No SKM to decrypt ECIES");
@ -80,6 +83,27 @@ public class GarlicMessageParser {
_log.warn("ECIES decrypt fail");
return null;
}
} else if (type.isPQ()) {
RatchetSKM rskm;
if (skm instanceof RatchetSKM) {
rskm = (RatchetSKM) skm;
} else if (skm instanceof MuxedPQSKM) {
rskm = ((MuxedPQSKM) skm).getPQSKM();
} else {
if (_log.shouldWarn())
_log.warn("No SKM to decrypt PQ");
return null;
}
CloveSet rv = _context.eciesEngine().decrypt(encData, encryptionKey, rskm);
if (rv != null) {
if (_log.shouldDebug())
_log.debug("PQ decrypt success, cloves: " + rv.getCloveCount());
return rv;
} else {
if (_log.shouldWarn())
_log.warn("PQ decrypt fail");
return null;
}
} else {
if (_log.shouldWarn())
_log.warn("Can't decrypt with key type " + type);
@ -112,7 +136,7 @@ public class GarlicMessageParser {
/**
* Supports both ELGAMAL_2048 and ECIES_X25519.
*
* @param elgKey must be ElG, non-null
* @param elgKey must be ElG OR PQ, non-null
* @param ecKey must be EC, non-null
* @param skm use tags from this session key manager
* @return null on error
@ -125,6 +149,10 @@ public class GarlicMessageParser {
if (skm instanceof MuxedSKM) {
MuxedSKM mskm = (MuxedSKM) skm;
rv = _context.eciesEngine().decrypt(encData, elgKey, ecKey, mskm);
} else if (skm instanceof MuxedPQSKM) {
MuxedPQSKM mskm = (MuxedPQSKM) skm;
// EC is first
rv = _context.eciesEngine().decrypt(encData, ecKey, elgKey, mskm);
} else if (skm instanceof RatchetSKM) {
// unlikely, if we have two keys we should have a MuxedSKM
RatchetSKM rskm = (RatchetSKM) skm;

View File

@ -65,15 +65,24 @@ public class GarlicMessageReceiver {
if (keys != null && skm != null) {
decryptionKey = keys.getDecryptionKey();
decryptionKey2 = keys.getDecryptionKey(EncType.ECIES_X25519);
if (decryptionKey == null && decryptionKey2 == null) {
// this will return any of the PQ types
PrivateKey decryptionKey3 = keys.getPQDecryptionKey();
if (decryptionKey == null && decryptionKey2 == null && decryptionKey3 == null) {
if (_log.shouldWarn())
_log.warn("No key to decrypt for " + _clientDestination.toBase32());
return;
}
// ElG + PQ disallowed
if (decryptionKey == null) {
// swap
decryptionKey = decryptionKey2;
decryptionKey2 = null;
if (decryptionKey3 != null) {
// PQ first if present
decryptionKey = decryptionKey3;
} else {
// EC only
decryptionKey = decryptionKey2;
decryptionKey2 = null;
}
}
} else {
if (_log.shouldLog(Log.WARN))

View File

@ -124,7 +124,7 @@ class OutboundClientMessageJobHelper {
SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(from);
if (skm == null)
return null;
boolean isECIES = recipientPK.getType() == EncType.ECIES_X25519;
boolean isECIES = recipientPK.getType() != EncType.ELGAMAL_2048;
// force ack off if ECIES
boolean ackInGarlic = isECIES ? false : requireAck;
GarlicConfig config = createGarlicConfig(ctx, replyToken, expiration, recipientPK, dataClove,

View File

@ -359,7 +359,7 @@ public class IterativeSearchJob extends FloodSearchJob {
}
LeaseSetKeys lsk = ctx.keyManager().getKeys(_fromLocalDest);
supportsRatchet = lsk != null &&
lsk.isSupported(EncType.ECIES_X25519) &&
(lsk.isSupported(EncType.ECIES_X25519) || lsk.getPQDecryptionKey() != null) &&
DatabaseLookupMessage.supportsRatchetReplies(ri);
supportsElGamal = !supportsRatchet &&
lsk != null &&

View File

@ -17,6 +17,7 @@ import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.RouterContext;
import net.i2p.router.crypto.TransientSessionKeyManager;
import net.i2p.router.crypto.ratchet.MuxedPQSKM;
import net.i2p.router.crypto.ratchet.MuxedSKM;
import net.i2p.router.crypto.ratchet.RatchetSKM;
import net.i2p.router.crypto.ratchet.RatchetSessionTag;
@ -242,6 +243,8 @@ public class MessageWrapper {
rskm = (RatchetSKM) skm;
} else if (skm instanceof MuxedSKM) {
rskm = ((MuxedSKM) skm).getECSKM();
} else if (skm instanceof MuxedPQSKM) {
rskm = ((MuxedPQSKM) skm).getECSKM();
} else {
throw new IllegalStateException("skm not a ratchet " + skm);
}

View File

@ -27,6 +27,7 @@ import net.i2p.router.TunnelInfo;
import net.i2p.router.TunnelManagerFacade;
import net.i2p.router.TunnelPoolSettings;
import net.i2p.router.crypto.ratchet.RatchetSKM;
import net.i2p.router.crypto.ratchet.MuxedPQSKM;
import net.i2p.router.crypto.ratchet.MuxedSKM;
import net.i2p.router.networkdb.kademlia.MessageWrapper;
import net.i2p.router.networkdb.kademlia.MessageWrapper.OneTimeSession;
@ -318,6 +319,9 @@ abstract class BuildRequestor {
} else if (replySKM instanceof MuxedSKM) {
MuxedSKM mskm = (MuxedSKM) replySKM;
mskm.tagsReceived(ots.key, ots.rtag, 2 * BUILD_MSG_TIMEOUT);
} else if (replySKM instanceof MuxedPQSKM) {
MuxedPQSKM mskm = (MuxedPQSKM) replySKM;
mskm.tagsReceived(ots.key, ots.rtag, 2 * BUILD_MSG_TIMEOUT);
} else {
// non-EC client, shouldn't happen, checked at top of createTunnelBuildMessage() below
if (log.shouldWarn())

View File

@ -13,6 +13,7 @@ import net.i2p.router.OutNetMessage;
import net.i2p.router.ReplyJob;
import net.i2p.router.RouterContext;
import net.i2p.router.TunnelInfo;
import net.i2p.router.crypto.ratchet.MuxedPQSKM;
import net.i2p.router.crypto.ratchet.MuxedSKM;
import net.i2p.router.crypto.ratchet.RatchetSessionTag;
import net.i2p.router.crypto.ratchet.RatchetSKM;
@ -368,6 +369,8 @@ class TestJob extends JobImpl {
rskm = (RatchetSKM) skm;
} else if (skm instanceof MuxedSKM) {
rskm = ((MuxedSKM) skm).getECSKM();
} else if (skm instanceof MuxedPQSKM) {
rskm = ((MuxedPQSKM) skm).getECSKM();
} else {
// shouldn't happen
rskm = null;

View File

@ -0,0 +1,61 @@
package org.bouncycastle.crypto;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
/**
* a holding class for public/private parameter pairs.
*/
public class AsymmetricCipherKeyPair
{
private AsymmetricKeyParameter publicParam;
private AsymmetricKeyParameter privateParam;
/**
* basic constructor.
*
* @param publicParam a public key parameters object.
* @param privateParam the corresponding private key parameters.
*/
public AsymmetricCipherKeyPair(
AsymmetricKeyParameter publicParam,
AsymmetricKeyParameter privateParam)
{
this.publicParam = publicParam;
this.privateParam = privateParam;
}
/**
* basic constructor.
*
* @param publicParam a public key parameters object.
* @param privateParam the corresponding private key parameters.
* @deprecated use AsymmetricKeyParameter
*/
public AsymmetricCipherKeyPair(
CipherParameters publicParam,
CipherParameters privateParam)
{
this.publicParam = (AsymmetricKeyParameter)publicParam;
this.privateParam = (AsymmetricKeyParameter)privateParam;
}
/**
* return the public key parameters.
*
* @return the public key parameters.
*/
public AsymmetricKeyParameter getPublic()
{
return publicParam;
}
/**
* return the private key parameters.
*
* @return the private key parameters.
*/
public AsymmetricKeyParameter getPrivate()
{
return privateParam;
}
}

View File

@ -0,0 +1,22 @@
package org.bouncycastle.crypto;
/**
* interface that a public/private key pair generator should conform to.
*/
public interface AsymmetricCipherKeyPairGenerator
{
/**
* intialise the key pair generator.
*
* @param param the parameters the key pair is to be initialised with.
*/
public void init(KeyGenerationParameters param);
/**
* return an AsymmetricCipherKeyPair containing the generated keys.
*
* @return an AsymmetricCipherKeyPair containing the generated keys.
*/
public AsymmetricCipherKeyPair generateKeyPair();
}

View File

@ -0,0 +1,8 @@
package org.bouncycastle.crypto;
/**
* all parameter classes implement this.
*/
public interface CipherParameters
{
}

View File

@ -0,0 +1,48 @@
package org.bouncycastle.crypto;
/**
* the foundation class for the hard exceptions thrown by the crypto packages.
*/
public class CryptoException
extends Exception
{
private Throwable cause;
/**
* base constructor.
*/
public CryptoException()
{
}
/**
* create a CryptoException with the given message.
*
* @param message the message to be carried with the exception.
*/
public CryptoException(
String message)
{
super(message);
}
/**
* Create a CryptoException with the given message and underlying cause.
*
* @param message message describing exception.
* @param cause the throwable that was the underlying cause.
*/
public CryptoException(
String message,
Throwable cause)
{
super(message);
this.cause = cause;
}
public Throwable getCause()
{
return cause;
}
}

View File

@ -0,0 +1,12 @@
package org.bouncycastle.crypto;
public interface CryptoServiceProperties
{
int bitsOfSecurity();
String getServiceName();
CryptoServicePurpose getPurpose();
Object getParams();
}

View File

@ -0,0 +1,15 @@
package org.bouncycastle.crypto;
public enum CryptoServicePurpose
{
AGREEMENT,
ENCRYPTION,
DECRYPTION,
KEYGEN,
SIGNING, // for signatures (and digests)
VERIFYING,
AUTHENTICATION, // for MACs (and digests)
VERIFICATION,
PRF,
ANY
}

View File

@ -0,0 +1,20 @@
package org.bouncycastle.crypto;
import java.security.SecureRandom;
public class CryptoServicesRegistrar {
public static void checkConstraints(CryptoServiceProperties csp) {}
private static final SecureRandom sr = new SecureRandom();
/**
* Return the default source of randomness.
*
* @return the default SecureRandom
*/
public static SecureRandom getSecureRandom(SecureRandom secureRandom)
{
return null == secureRandom ? sr : secureRandom;
}
}

View File

@ -0,0 +1,29 @@
package org.bouncycastle.crypto;
/**
* this exception is thrown if a buffer that is meant to have output
* copied into it turns out to be too short, or if we've been given
* insufficient input. In general this exception will get thrown rather
* than an ArrayOutOfBounds exception.
*/
public class DataLengthException
extends RuntimeCryptoException
{
/**
* base constructor.
*/
public DataLengthException()
{
}
/**
* create a DataLengthException with the given message.
*
* @param message the message to be carried with the exception.
*/
public DataLengthException(
String message)
{
super(message);
}
}

View File

@ -0,0 +1,51 @@
package org.bouncycastle.crypto;
/**
* interface that a message digest conforms to.
*/
public interface Digest
{
/**
* return the algorithm name
*
* @return the algorithm name
*/
public String getAlgorithmName();
/**
* return the size, in bytes, of the digest produced by this message digest.
*
* @return the size, in bytes, of the digest produced by this message digest.
*/
public int getDigestSize();
/**
* update the message digest with a single byte.
*
* @param in the input byte to be entered.
*/
public void update(byte in);
/**
* update the message digest with a block of bytes.
*
* @param in the byte array containing the data.
* @param inOff the offset into the byte array where the data starts.
* @param len the length of the data.
*/
public void update(byte[] in, int inOff, int len);
/**
* close the digest, producing the final digest value. The doFinal
* call leaves the digest reset.
*
* @param out the array the digest is to be copied into.
* @param outOff the offset into the out array the digest is to start at.
*/
public int doFinal(byte[] out, int outOff);
/**
* reset the digest back to it's initial state.
*/
public void reset();
}

View File

@ -0,0 +1,13 @@
package org.bouncycastle.crypto;
public interface ExtendedDigest
extends Digest
{
/**
* Return the size in bytes of the internal buffer the digest applies it's compression
* function to.
*
* @return byte length of the digests internal buffer.
*/
public int getByteLength();
}

View File

@ -0,0 +1,48 @@
package org.bouncycastle.crypto;
import java.security.SecureRandom;
/**
* The base class for parameters to key generators.
*/
public class KeyGenerationParameters
{
private SecureRandom random;
private int strength;
/**
* initialise the generator with a source of randomness
* and a strength (in bits).
*
* @param random the random byte source.
* @param strength the size, in bits, of the keys we want to produce.
*/
public KeyGenerationParameters(
SecureRandom random,
int strength)
{
this.random = CryptoServicesRegistrar.getSecureRandom(random);
this.strength = strength;
}
/**
* return the random source associated with this
* generator.
*
* @return the generators random source.
*/
public SecureRandom getRandom()
{
return random;
}
/**
* return the bit strength for keys produced by this generator,
*
* @return the strength of the keys this generator produces (in bits).
*/
public int getStrength()
{
return strength;
}
}

View File

@ -0,0 +1,26 @@
package org.bouncycastle.crypto;
/**
* the foundation class for the exceptions thrown by the crypto packages.
*/
public class RuntimeCryptoException
extends RuntimeException
{
/**
* base constructor.
*/
public RuntimeCryptoException()
{
}
/**
* create a RuntimeCryptoException with the given message.
*
* @param message the message to be carried with the exception.
*/
public RuntimeCryptoException(
String message)
{
super(message);
}
}

View File

@ -0,0 +1,24 @@
package org.bouncycastle.crypto;
import javax.security.auth.Destroyable;
/**
* Interface describing secret with encapsulation details.
*/
public interface SecretWithEncapsulation
extends Destroyable
{
/**
* Return the secret associated with the encapsulation.
*
* @return the secret the encapsulation is for.
*/
byte[] getSecret();
/**
* Return the data that carries the secret in its encapsulated form.
*
* @return the encapsulation of the secret.
*/
byte[] getEncapsulation();
}

View File

@ -0,0 +1,30 @@
package org.bouncycastle.crypto;
/**
* With FIPS PUB 202 a new kind of message digest was announced which supported extendable output, or variable digest sizes.
* This interface provides the extra method required to support variable output on an extended digest implementation.
*/
public interface Xof
extends ExtendedDigest
{
/**
* Output the results of the final calculation for this digest to outLen number of bytes.
*
* @param out output array to write the output bytes to.
* @param outOff offset to start writing the bytes at.
* @param outLen the number of output bytes requested.
* @return the number of bytes written
*/
int doFinal(byte[] out, int outOff, int outLen);
/**
* Start outputting the results of the final calculation for this digest. Unlike doFinal, this method
* will continue producing output until the Xof is explicitly reset, or signals otherwise.
*
* @param out output array to write the output bytes to.
* @param outOff offset to start writing the bytes at.
* @param outLen the number of output bytes requested.
* @return the number of bytes written
*/
int doOutput(byte[] out, int outOff, int outLen);
}

View File

@ -0,0 +1,17 @@
package org.bouncycastle.crypto.digests;
/**
* Encodable digests allow you to download an encoded copy of their internal state. This is useful for the situation where
* you need to generate a signature on an external device and it allows for "sign with last round", so a copy of the
* internal state of the digest, plus the last few blocks of the message are all that needs to be sent, rather than the
* entire message.
*/
public interface EncodableDigest
{
/**
* Return an encoded byte array for the digest's internal state
*
* @return an encoding of the digests internal state.
*/
byte[] getEncodedState();
}

View File

@ -0,0 +1,444 @@
package org.bouncycastle.crypto.digests;
import java.util.Arrays;
import org.bouncycastle.crypto.CryptoServiceProperties;
import org.bouncycastle.crypto.CryptoServicePurpose;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.bouncycastle.crypto.ExtendedDigest;
import org.bouncycastle.util.Pack;
/**
* implementation of Keccak based on following KeccakNISTInterface.c from https://keccak.noekeon.org/
* <p>
* Following the naming conventions used in the C source code to enable easy review of the implementation.
*/
public class KeccakDigest
implements ExtendedDigest
{
private static long[] KeccakRoundConstants = new long[]{ 0x0000000000000001L, 0x0000000000008082L,
0x800000000000808aL, 0x8000000080008000L, 0x000000000000808bL, 0x0000000080000001L, 0x8000000080008081L,
0x8000000000008009L, 0x000000000000008aL, 0x0000000000000088L, 0x0000000080008009L, 0x000000008000000aL,
0x000000008000808bL, 0x800000000000008bL, 0x8000000000008089L, 0x8000000000008003L, 0x8000000000008002L,
0x8000000000000080L, 0x000000000000800aL, 0x800000008000000aL, 0x8000000080008081L, 0x8000000000008080L,
0x0000000080000001L, 0x8000000080008008L };
protected final CryptoServicePurpose purpose;
protected long[] state = new long[25];
protected byte[] dataQueue = new byte[192];
protected int rate;
protected int bitsInQueue;
protected int fixedOutputLength;
protected boolean squeezing;
public KeccakDigest()
{
this(288, CryptoServicePurpose.ANY);
}
public KeccakDigest(CryptoServicePurpose purpose)
{
this(288, purpose);
}
public KeccakDigest(int bitLength)
{
this(bitLength, CryptoServicePurpose.ANY);
}
public KeccakDigest(int bitLength, CryptoServicePurpose purpose)
{
this.purpose = purpose;
init(bitLength);
CryptoServicesRegistrar.checkConstraints(cryptoServiceProperties());
}
public KeccakDigest(KeccakDigest source)
{
this.purpose = source.purpose;
System.arraycopy(source.state, 0, this.state, 0, source.state.length);
System.arraycopy(source.dataQueue, 0, this.dataQueue, 0, source.dataQueue.length);
this.rate = source.rate;
this.bitsInQueue = source.bitsInQueue;
this.fixedOutputLength = source.fixedOutputLength;
this.squeezing = source.squeezing;
CryptoServicesRegistrar.checkConstraints(cryptoServiceProperties());
}
public String getAlgorithmName()
{
return "Keccak-" + fixedOutputLength;
}
public int getDigestSize()
{
return fixedOutputLength / 8;
}
public void update(byte in)
{
absorb(in);
}
public void update(byte[] in, int inOff, int len)
{
absorb(in, inOff, len);
}
public int doFinal(byte[] out, int outOff)
{
squeeze(out, outOff, fixedOutputLength);
reset();
return getDigestSize();
}
/*
* TODO Possible API change to support partial-byte suffixes.
*/
protected int doFinal(byte[] out, int outOff, byte partialByte, int partialBits)
{
if (partialBits > 0)
{
absorbBits(partialByte, partialBits);
}
squeeze(out, outOff, fixedOutputLength);
reset();
return getDigestSize();
}
public void reset()
{
init(fixedOutputLength);
}
/**
* Return the size of block that the compression function is applied to in bytes.
*
* @return internal byte length of a block.
*/
public int getByteLength()
{
return rate / 8;
}
private void init(int bitLength)
{
switch (bitLength)
{
case 128:
case 224:
case 256:
case 288:
case 384:
case 512:
initSponge(1600 - (bitLength << 1));
break;
default:
throw new IllegalArgumentException("bitLength must be one of 128, 224, 256, 288, 384, or 512.");
}
}
private void initSponge(int rate)
{
if ((rate <= 0) || (rate >= 1600) || ((rate % 64) != 0))
{
throw new IllegalStateException("invalid rate value");
}
this.rate = rate;
for (int i = 0; i < state.length; ++i)
{
state[i] = 0L;
}
Arrays.fill(this.dataQueue, (byte)0);
this.bitsInQueue = 0;
this.squeezing = false;
this.fixedOutputLength = (1600 - rate) / 2;
}
protected void absorb(byte data)
{
if ((bitsInQueue % 8) != 0)
{
throw new IllegalStateException("attempt to absorb with odd length queue");
}
if (squeezing)
{
throw new IllegalStateException("attempt to absorb while squeezing");
}
dataQueue[bitsInQueue >>> 3] = data;
if ((bitsInQueue += 8) == rate)
{
KeccakAbsorb(dataQueue, 0);
bitsInQueue = 0;
}
}
protected void absorb(byte[] data, int off, int len)
{
if ((bitsInQueue % 8) != 0)
{
throw new IllegalStateException("attempt to absorb with odd length queue");
}
if (squeezing)
{
throw new IllegalStateException("attempt to absorb while squeezing");
}
int bytesInQueue = bitsInQueue >>> 3;
int rateBytes = rate >>> 3;
int available = rateBytes - bytesInQueue;
if (len < available)
{
System.arraycopy(data, off, dataQueue, bytesInQueue, len);
this.bitsInQueue += len << 3;
return;
}
int count = 0;
if (bytesInQueue > 0)
{
System.arraycopy(data, off, dataQueue, bytesInQueue, available);
count += available;
KeccakAbsorb(dataQueue, 0);
}
int remaining;
while ((remaining = (len - count)) >= rateBytes)
{
KeccakAbsorb(data, off + count);
count += rateBytes;
}
System.arraycopy(data, off + count, dataQueue, 0, remaining);
this.bitsInQueue = remaining << 3;
}
protected void absorbBits(int data, int bits)
{
if (bits < 1 || bits > 7)
{
throw new IllegalArgumentException("'bits' must be in the range 1 to 7");
}
if ((bitsInQueue % 8) != 0)
{
throw new IllegalStateException("attempt to absorb with odd length queue");
}
if (squeezing)
{
throw new IllegalStateException("attempt to absorb while squeezing");
}
int mask = (1 << bits) - 1;
dataQueue[bitsInQueue >>> 3] = (byte)(data & mask);
// NOTE: After this, bitsInQueue is no longer a multiple of 8, so no more absorbs will work
bitsInQueue += bits;
}
private void padAndSwitchToSqueezingPhase()
{
dataQueue[bitsInQueue >>> 3] |= (byte)(1 << (bitsInQueue & 7));
if (++bitsInQueue == rate)
{
KeccakAbsorb(dataQueue, 0);
}
else
{
int full = bitsInQueue >>> 6, partial = bitsInQueue & 63;
int off = 0;
for (int i = 0; i < full; ++i)
{
state[i] ^= Pack.littleEndianToLong(dataQueue, off);
off += 8;
}
if (partial > 0)
{
long mask = (1L << partial) - 1L;
state[full] ^= Pack.littleEndianToLong(dataQueue, off) & mask;
}
}
state[(rate - 1) >>> 6] ^= (1L << 63);
bitsInQueue = 0;
squeezing = true;
}
protected void squeeze(byte[] output, int offset, long outputLength)
{
if (!squeezing)
{
padAndSwitchToSqueezingPhase();
}
if ((outputLength % 8) != 0)
{
throw new IllegalStateException("outputLength not a multiple of 8");
}
long i = 0;
while (i < outputLength)
{
if (bitsInQueue == 0)
{
KeccakExtract();
}
int partialBlock = (int)Math.min((long)bitsInQueue, outputLength - i);
System.arraycopy(dataQueue, (rate - bitsInQueue) / 8, output, offset + (int)(i / 8), partialBlock / 8);
bitsInQueue -= partialBlock;
i += partialBlock;
}
}
private void KeccakAbsorb(byte[] data, int off)
{
// assert 0 == bitsInQueue || (dataQueue == data && 0 == off);
int count = rate >>> 6;
for (int i = 0; i < count; ++i)
{
state[i] ^= Pack.littleEndianToLong(data, off);
off += 8;
}
KeccakPermutation();
}
private void KeccakExtract()
{
// assert 0 == bitsInQueue;
KeccakPermutation();
Pack.longToLittleEndian(state, 0, rate >>> 6, dataQueue, 0);
this.bitsInQueue = rate;
}
private void KeccakPermutation()
{
long[] A = state;
long a00 = A[ 0], a01 = A[ 1], a02 = A[ 2], a03 = A[ 3], a04 = A[ 4];
long a05 = A[ 5], a06 = A[ 6], a07 = A[ 7], a08 = A[ 8], a09 = A[ 9];
long a10 = A[10], a11 = A[11], a12 = A[12], a13 = A[13], a14 = A[14];
long a15 = A[15], a16 = A[16], a17 = A[17], a18 = A[18], a19 = A[19];
long a20 = A[20], a21 = A[21], a22 = A[22], a23 = A[23], a24 = A[24];
for (int i = 0; i < 24; i++)
{
// theta
long c0 = a00 ^ a05 ^ a10 ^ a15 ^ a20;
long c1 = a01 ^ a06 ^ a11 ^ a16 ^ a21;
long c2 = a02 ^ a07 ^ a12 ^ a17 ^ a22;
long c3 = a03 ^ a08 ^ a13 ^ a18 ^ a23;
long c4 = a04 ^ a09 ^ a14 ^ a19 ^ a24;
long d1 = (c1 << 1 | c1 >>> -1) ^ c4;
long d2 = (c2 << 1 | c2 >>> -1) ^ c0;
long d3 = (c3 << 1 | c3 >>> -1) ^ c1;
long d4 = (c4 << 1 | c4 >>> -1) ^ c2;
long d0 = (c0 << 1 | c0 >>> -1) ^ c3;
a00 ^= d1; a05 ^= d1; a10 ^= d1; a15 ^= d1; a20 ^= d1;
a01 ^= d2; a06 ^= d2; a11 ^= d2; a16 ^= d2; a21 ^= d2;
a02 ^= d3; a07 ^= d3; a12 ^= d3; a17 ^= d3; a22 ^= d3;
a03 ^= d4; a08 ^= d4; a13 ^= d4; a18 ^= d4; a23 ^= d4;
a04 ^= d0; a09 ^= d0; a14 ^= d0; a19 ^= d0; a24 ^= d0;
// rho/pi
c1 = a01 << 1 | a01 >>> 63;
a01 = a06 << 44 | a06 >>> 20;
a06 = a09 << 20 | a09 >>> 44;
a09 = a22 << 61 | a22 >>> 3;
a22 = a14 << 39 | a14 >>> 25;
a14 = a20 << 18 | a20 >>> 46;
a20 = a02 << 62 | a02 >>> 2;
a02 = a12 << 43 | a12 >>> 21;
a12 = a13 << 25 | a13 >>> 39;
a13 = a19 << 8 | a19 >>> 56;
a19 = a23 << 56 | a23 >>> 8;
a23 = a15 << 41 | a15 >>> 23;
a15 = a04 << 27 | a04 >>> 37;
a04 = a24 << 14 | a24 >>> 50;
a24 = a21 << 2 | a21 >>> 62;
a21 = a08 << 55 | a08 >>> 9;
a08 = a16 << 45 | a16 >>> 19;
a16 = a05 << 36 | a05 >>> 28;
a05 = a03 << 28 | a03 >>> 36;
a03 = a18 << 21 | a18 >>> 43;
a18 = a17 << 15 | a17 >>> 49;
a17 = a11 << 10 | a11 >>> 54;
a11 = a07 << 6 | a07 >>> 58;
a07 = a10 << 3 | a10 >>> 61;
a10 = c1;
// chi
c0 = a00 ^ (~a01 & a02);
c1 = a01 ^ (~a02 & a03);
a02 ^= ~a03 & a04;
a03 ^= ~a04 & a00;
a04 ^= ~a00 & a01;
a00 = c0;
a01 = c1;
c0 = a05 ^ (~a06 & a07);
c1 = a06 ^ (~a07 & a08);
a07 ^= ~a08 & a09;
a08 ^= ~a09 & a05;
a09 ^= ~a05 & a06;
a05 = c0;
a06 = c1;
c0 = a10 ^ (~a11 & a12);
c1 = a11 ^ (~a12 & a13);
a12 ^= ~a13 & a14;
a13 ^= ~a14 & a10;
a14 ^= ~a10 & a11;
a10 = c0;
a11 = c1;
c0 = a15 ^ (~a16 & a17);
c1 = a16 ^ (~a17 & a18);
a17 ^= ~a18 & a19;
a18 ^= ~a19 & a15;
a19 ^= ~a15 & a16;
a15 = c0;
a16 = c1;
c0 = a20 ^ (~a21 & a22);
c1 = a21 ^ (~a22 & a23);
a22 ^= ~a23 & a24;
a23 ^= ~a24 & a20;
a24 ^= ~a20 & a21;
a20 = c0;
a21 = c1;
// iota
a00 ^= KeccakRoundConstants[i];
}
A[ 0] = a00; A[ 1] = a01; A[ 2] = a02; A[ 3] = a03; A[ 4] = a04;
A[ 5] = a05; A[ 6] = a06; A[ 7] = a07; A[ 8] = a08; A[ 9] = a09;
A[10] = a10; A[11] = a11; A[12] = a12; A[13] = a13; A[14] = a14;
A[15] = a15; A[16] = a16; A[17] = a17; A[18] = a18; A[19] = a19;
A[20] = a20; A[21] = a21; A[22] = a22; A[23] = a23; A[24] = a24;
}
protected CryptoServiceProperties cryptoServiceProperties()
{
return null;
}
}

View File

@ -0,0 +1,426 @@
package org.bouncycastle.crypto.digests;
import org.bouncycastle.crypto.CryptoServiceProperties;
import org.bouncycastle.crypto.CryptoServicePurpose;
import org.bouncycastle.crypto.ExtendedDigest;
import org.bouncycastle.util.Memoable;
import org.bouncycastle.util.Pack;
/**
* Base class for SHA-384 and SHA-512.
*/
public abstract class LongDigest
implements ExtendedDigest, Memoable, EncodableDigest
{
private static final int BYTE_LENGTH = 128;
protected final CryptoServicePurpose purpose;
private byte[] xBuf = new byte[8];
private int xBufOff;
private long byteCount1;
private long byteCount2;
protected long H1, H2, H3, H4, H5, H6, H7, H8;
private long[] W = new long[80];
private int wOff;
/**
* Constructor for variable length word
*/
protected LongDigest()
{
this(CryptoServicePurpose.ANY);
}
/**
* Constructor for variable length word
*/
protected LongDigest(CryptoServicePurpose purpose)
{
this.purpose = purpose;
xBufOff = 0;
reset();
}
/**
* Copy constructor. We are using copy constructors in place
* of the Object.clone() interface as this interface is not
* supported by J2ME.
*/
protected LongDigest(LongDigest t)
{
this.purpose = t.purpose;
copyIn(t);
}
protected void copyIn(LongDigest t)
{
System.arraycopy(t.xBuf, 0, xBuf, 0, t.xBuf.length);
xBufOff = t.xBufOff;
byteCount1 = t.byteCount1;
byteCount2 = t.byteCount2;
H1 = t.H1;
H2 = t.H2;
H3 = t.H3;
H4 = t.H4;
H5 = t.H5;
H6 = t.H6;
H7 = t.H7;
H8 = t.H8;
System.arraycopy(t.W, 0, W, 0, t.W.length);
wOff = t.wOff;
}
protected void populateState(byte[] state)
{
System.arraycopy(xBuf, 0, state, 0, xBufOff);
Pack.intToBigEndian(xBufOff, state, 8);
Pack.longToBigEndian(byteCount1, state, 12);
Pack.longToBigEndian(byteCount2, state, 20);
Pack.longToBigEndian(H1, state, 28);
Pack.longToBigEndian(H2, state, 36);
Pack.longToBigEndian(H3, state, 44);
Pack.longToBigEndian(H4, state, 52);
Pack.longToBigEndian(H5, state, 60);
Pack.longToBigEndian(H6, state, 68);
Pack.longToBigEndian(H7, state, 76);
Pack.longToBigEndian(H8, state, 84);
Pack.intToBigEndian(wOff, state, 92);
for (int i = 0; i < wOff; i++)
{
Pack.longToBigEndian(W[i], state, 96 + (i * 8));
}
}
protected void restoreState(byte[] encodedState)
{
xBufOff = Pack.bigEndianToInt(encodedState, 8);
System.arraycopy(encodedState, 0, xBuf, 0, xBufOff);
byteCount1 = Pack.bigEndianToLong(encodedState, 12);
byteCount2 = Pack.bigEndianToLong(encodedState, 20);
H1 = Pack.bigEndianToLong(encodedState, 28);
H2 = Pack.bigEndianToLong(encodedState, 36);
H3 = Pack.bigEndianToLong(encodedState, 44);
H4 = Pack.bigEndianToLong(encodedState, 52);
H5 = Pack.bigEndianToLong(encodedState, 60);
H6 = Pack.bigEndianToLong(encodedState, 68);
H7 = Pack.bigEndianToLong(encodedState, 76);
H8 = Pack.bigEndianToLong(encodedState, 84);
wOff = Pack.bigEndianToInt(encodedState, 92);
for (int i = 0; i < wOff; i++)
{
W[i] = Pack.bigEndianToLong(encodedState, 96 + (i * 8));
}
}
protected int getEncodedStateSize()
{
return 96 + (wOff * 8);
}
public void update(
byte in)
{
xBuf[xBufOff++] = in;
if (xBufOff == xBuf.length)
{
processWord(xBuf, 0);
xBufOff = 0;
}
byteCount1++;
}
public void update(
byte[] in,
int inOff,
int len)
{
//
// fill the current word
//
while ((xBufOff != 0) && (len > 0))
{
update(in[inOff]);
inOff++;
len--;
}
//
// process whole words.
//
while (len >= xBuf.length)
{
processWord(in, inOff);
inOff += xBuf.length;
len -= xBuf.length;
byteCount1 += xBuf.length;
}
//
// load in the remainder.
//
while (len > 0)
{
update(in[inOff]);
inOff++;
len--;
}
}
public void finish()
{
adjustByteCounts();
long lowBitLength = byteCount1 << 3;
long hiBitLength = byteCount2;
//
// add the pad bytes.
//
update((byte)128);
while (xBufOff != 0)
{
update((byte)0);
}
processLength(lowBitLength, hiBitLength);
processBlock();
}
public void reset()
{
byteCount1 = 0;
byteCount2 = 0;
xBufOff = 0;
for (int i = 0; i < xBuf.length; i++)
{
xBuf[i] = 0;
}
wOff = 0;
for (int i = 0; i != W.length; i++)
{
W[i] = 0;
}
}
public int getByteLength()
{
return BYTE_LENGTH;
}
protected void processWord(
byte[] in,
int inOff)
{
W[wOff] = Pack.bigEndianToLong(in, inOff);
if (++wOff == 16)
{
processBlock();
}
}
/**
* adjust the byte counts so that byteCount2 represents the
* upper long (less 3 bits) word of the byte count.
*/
private void adjustByteCounts()
{
if (byteCount1 > 0x1fffffffffffffffL)
{
byteCount2 += (byteCount1 >>> 61);
byteCount1 &= 0x1fffffffffffffffL;
}
}
protected void processLength(
long lowW,
long hiW)
{
if (wOff > 14)
{
processBlock();
}
W[14] = hiW;
W[15] = lowW;
}
protected void processBlock()
{
adjustByteCounts();
//
// expand 16 word block into 80 word blocks.
//
for (int t = 16; t <= 79; t++)
{
W[t] = Sigma1(W[t - 2]) + W[t - 7] + Sigma0(W[t - 15]) + W[t - 16];
}
//
// set up working variables.
//
long a = H1;
long b = H2;
long c = H3;
long d = H4;
long e = H5;
long f = H6;
long g = H7;
long h = H8;
int t = 0;
for(int i = 0; i < 10; i ++)
{
// t = 8 * i
h += Sum1(e) + Ch(e, f, g) + K[t] + W[t++];
d += h;
h += Sum0(a) + Maj(a, b, c);
// t = 8 * i + 1
g += Sum1(d) + Ch(d, e, f) + K[t] + W[t++];
c += g;
g += Sum0(h) + Maj(h, a, b);
// t = 8 * i + 2
f += Sum1(c) + Ch(c, d, e) + K[t] + W[t++];
b += f;
f += Sum0(g) + Maj(g, h, a);
// t = 8 * i + 3
e += Sum1(b) + Ch(b, c, d) + K[t] + W[t++];
a += e;
e += Sum0(f) + Maj(f, g, h);
// t = 8 * i + 4
d += Sum1(a) + Ch(a, b, c) + K[t] + W[t++];
h += d;
d += Sum0(e) + Maj(e, f, g);
// t = 8 * i + 5
c += Sum1(h) + Ch(h, a, b) + K[t] + W[t++];
g += c;
c += Sum0(d) + Maj(d, e, f);
// t = 8 * i + 6
b += Sum1(g) + Ch(g, h, a) + K[t] + W[t++];
f += b;
b += Sum0(c) + Maj(c, d, e);
// t = 8 * i + 7
a += Sum1(f) + Ch(f, g, h) + K[t] + W[t++];
e += a;
a += Sum0(b) + Maj(b, c, d);
}
H1 += a;
H2 += b;
H3 += c;
H4 += d;
H5 += e;
H6 += f;
H7 += g;
H8 += h;
//
// reset the offset and clean out the word buffer.
//
wOff = 0;
for (int i = 0; i < 16; i++)
{
W[i] = 0;
}
}
/* SHA-384 and SHA-512 functions (as for SHA-256 but for longs) */
private long Ch(
long x,
long y,
long z)
{
return ((x & y) ^ ((~x) & z));
}
private long Maj(
long x,
long y,
long z)
{
return ((x & y) ^ (x & z) ^ (y & z));
}
private long Sum0(
long x)
{
return ((x << 36)|(x >>> 28)) ^ ((x << 30)|(x >>> 34)) ^ ((x << 25)|(x >>> 39));
}
private long Sum1(
long x)
{
return ((x << 50)|(x >>> 14)) ^ ((x << 46)|(x >>> 18)) ^ ((x << 23)|(x >>> 41));
}
private long Sigma0(
long x)
{
return ((x << 63)|(x >>> 1)) ^ ((x << 56)|(x >>> 8)) ^ (x >>> 7);
}
private long Sigma1(
long x)
{
return ((x << 45)|(x >>> 19)) ^ ((x << 3)|(x >>> 61)) ^ (x >>> 6);
}
/* SHA-384 and SHA-512 Constants
* (represent the first 64 bits of the fractional parts of the
* cube roots of the first sixty-four prime numbers)
*/
static final long K[] = {
0x428a2f98d728ae22L, 0x7137449123ef65cdL, 0xb5c0fbcfec4d3b2fL, 0xe9b5dba58189dbbcL,
0x3956c25bf348b538L, 0x59f111f1b605d019L, 0x923f82a4af194f9bL, 0xab1c5ed5da6d8118L,
0xd807aa98a3030242L, 0x12835b0145706fbeL, 0x243185be4ee4b28cL, 0x550c7dc3d5ffb4e2L,
0x72be5d74f27b896fL, 0x80deb1fe3b1696b1L, 0x9bdc06a725c71235L, 0xc19bf174cf692694L,
0xe49b69c19ef14ad2L, 0xefbe4786384f25e3L, 0x0fc19dc68b8cd5b5L, 0x240ca1cc77ac9c65L,
0x2de92c6f592b0275L, 0x4a7484aa6ea6e483L, 0x5cb0a9dcbd41fbd4L, 0x76f988da831153b5L,
0x983e5152ee66dfabL, 0xa831c66d2db43210L, 0xb00327c898fb213fL, 0xbf597fc7beef0ee4L,
0xc6e00bf33da88fc2L, 0xd5a79147930aa725L, 0x06ca6351e003826fL, 0x142929670a0e6e70L,
0x27b70a8546d22ffcL, 0x2e1b21385c26c926L, 0x4d2c6dfc5ac42aedL, 0x53380d139d95b3dfL,
0x650a73548baf63deL, 0x766a0abb3c77b2a8L, 0x81c2c92e47edaee6L, 0x92722c851482353bL,
0xa2bfe8a14cf10364L, 0xa81a664bbc423001L, 0xc24b8b70d0f89791L, 0xc76c51a30654be30L,
0xd192e819d6ef5218L, 0xd69906245565a910L, 0xf40e35855771202aL, 0x106aa07032bbd1b8L,
0x19a4c116b8d2d0c8L, 0x1e376c085141ab53L, 0x2748774cdf8eeb99L, 0x34b0bcb5e19b48a8L,
0x391c0cb3c5c95a63L, 0x4ed8aa4ae3418acbL, 0x5b9cca4f7763e373L, 0x682e6ff3d6b2b8a3L,
0x748f82ee5defb2fcL, 0x78a5636f43172f60L, 0x84c87814a1f0ab72L, 0x8cc702081a6439ecL,
0x90befffa23631e28L, 0xa4506cebde82bde9L, 0xbef9a3f7b2c67915L, 0xc67178f2e372532bL,
0xca273eceea26619cL, 0xd186b8c721c0c207L, 0xeada7dd6cde0eb1eL, 0xf57d4f7fee6ed178L,
0x06f067aa72176fbaL, 0x0a637dc5a2c898a6L, 0x113f9804bef90daeL, 0x1b710b35131c471bL,
0x28db77f523047d84L, 0x32caab7b40c72493L, 0x3c9ebe0a15c9bebcL, 0x431d67c49c100d4cL,
0x4cc5d4becb3e42b6L, 0x597f299cfc657e2aL, 0x5fcb6fab3ad6faecL, 0x6c44198c4a475817L
};
protected abstract CryptoServiceProperties cryptoServiceProperties();
}

View File

@ -0,0 +1,87 @@
package org.bouncycastle.crypto.digests;
import org.bouncycastle.crypto.CryptoServicePurpose;
/**
* implementation of SHA-3 based on following KeccakNISTInterface.c from https://keccak.noekeon.org/
* <p>
* Following the naming conventions used in the C source code to enable easy review of the implementation.
*/
public class SHA3Digest
extends KeccakDigest
{
private static int checkBitLength(int bitLength)
{
switch (bitLength)
{
case 224:
case 256:
case 384:
case 512:
return bitLength;
default:
throw new IllegalArgumentException("'bitLength' " + bitLength + " not supported for SHA-3");
}
}
public SHA3Digest()
{
this(256, CryptoServicePurpose.ANY);
}
public SHA3Digest(CryptoServicePurpose purpose)
{
this(256, purpose);
}
public SHA3Digest(int bitLength)
{
super(checkBitLength(bitLength), CryptoServicePurpose.ANY);
}
public SHA3Digest(int bitLength, CryptoServicePurpose purpose)
{
super(checkBitLength(bitLength), purpose);
}
public SHA3Digest(SHA3Digest source)
{
super(source);
}
public String getAlgorithmName()
{
return "SHA3-" + fixedOutputLength;
}
public int doFinal(byte[] out, int outOff)
{
absorbBits(0x02, 2);
return super.doFinal(out, outOff);
}
/*
* TODO Possible API change to support partial-byte suffixes.
*/
protected int doFinal(byte[] out, int outOff, byte partialByte, int partialBits)
{
if (partialBits < 0 || partialBits > 7)
{
throw new IllegalArgumentException("'partialBits' must be in the range [0,7]");
}
int finalInput = (partialByte & ((1 << partialBits) - 1)) | (0x02 << partialBits);
int finalBits = partialBits + 2;
if (finalBits >= 8)
{
absorb((byte)finalInput);
finalBits -= 8;
finalInput >>>= 8;
}
return super.doFinal(out, outOff, (byte)finalInput, finalBits);
}
}

View File

@ -0,0 +1,150 @@
package org.bouncycastle.crypto.digests;
import org.bouncycastle.crypto.CryptoServiceProperties;
import org.bouncycastle.crypto.CryptoServicePurpose;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.bouncycastle.util.Memoable;
import org.bouncycastle.util.Pack;
/**
* FIPS 180-2 implementation of SHA-512.
*
* <pre>
* block word digest
* SHA-1 512 32 160
* SHA-256 512 32 256
* SHA-384 1024 64 384
* SHA-512 1024 64 512
* </pre>
*/
public class SHA512Digest
extends LongDigest
{
private static final int DIGEST_LENGTH = 64;
/**
* Standard constructor
*/
public SHA512Digest()
{
this(CryptoServicePurpose.ANY);
}
/**
* Standard constructor, with purpose
*/
public SHA512Digest(CryptoServicePurpose purpose)
{
super(purpose);
CryptoServicesRegistrar.checkConstraints(cryptoServiceProperties());
reset();
}
/**
* Copy constructor. This will copy the state of the provided
* message digest.
*/
public SHA512Digest(SHA512Digest t)
{
super(t);
CryptoServicesRegistrar.checkConstraints(cryptoServiceProperties());
}
/**
* State constructor - create a digest initialised with the state of a previous one.
*
* @param encodedState the encoded state from the originating digest.
*/
public SHA512Digest(byte[] encodedState)
{
super(CryptoServicePurpose.values()[encodedState[encodedState.length - 1]]);
restoreState(encodedState);
CryptoServicesRegistrar.checkConstraints(cryptoServiceProperties());
}
public String getAlgorithmName()
{
return "SHA-512";
}
public int getDigestSize()
{
return DIGEST_LENGTH;
}
public int doFinal(
byte[] out,
int outOff)
{
finish();
Pack.longToBigEndian(H1, out, outOff);
Pack.longToBigEndian(H2, out, outOff + 8);
Pack.longToBigEndian(H3, out, outOff + 16);
Pack.longToBigEndian(H4, out, outOff + 24);
Pack.longToBigEndian(H5, out, outOff + 32);
Pack.longToBigEndian(H6, out, outOff + 40);
Pack.longToBigEndian(H7, out, outOff + 48);
Pack.longToBigEndian(H8, out, outOff + 56);
reset();
return DIGEST_LENGTH;
}
/**
* reset the chaining variables
*/
public void reset()
{
super.reset();
/* SHA-512 initial hash value
* The first 64 bits of the fractional parts of the square roots
* of the first eight prime numbers
*/
H1 = 0x6a09e667f3bcc908L;
H2 = 0xbb67ae8584caa73bL;
H3 = 0x3c6ef372fe94f82bL;
H4 = 0xa54ff53a5f1d36f1L;
H5 = 0x510e527fade682d1L;
H6 = 0x9b05688c2b3e6c1fL;
H7 = 0x1f83d9abfb41bd6bL;
H8 = 0x5be0cd19137e2179L;
}
public Memoable copy()
{
return new SHA512Digest(this);
}
public void reset(Memoable other)
{
SHA512Digest d = (SHA512Digest)other;
copyIn(d);
}
public byte[] getEncodedState()
{
byte[] encoded = new byte[getEncodedStateSize() + 1];
super.populateState(encoded);
encoded[encoded.length - 1] = (byte)purpose.ordinal();
return encoded;
}
protected CryptoServiceProperties cryptoServiceProperties()
{
return null;
//return Utils.getDefaultProperties(this, 256, purpose);
}
}

View File

@ -0,0 +1,150 @@
package org.bouncycastle.crypto.digests;
import org.bouncycastle.crypto.CryptoServiceProperties;
import org.bouncycastle.crypto.CryptoServicePurpose;
import org.bouncycastle.crypto.Xof;
/**
* implementation of SHAKE based on following KeccakNISTInterface.c from https://keccak.noekeon.org/
* <p>
* Following the naming conventions used in the C source code to enable easy review of the implementation.
*/
public class SHAKEDigest
extends KeccakDigest
implements Xof
{
private static int checkBitLength(int bitStrength)
{
switch (bitStrength)
{
case 128:
case 256:
return bitStrength;
default:
throw new IllegalArgumentException("'bitStrength' " + bitStrength + " not supported for SHAKE");
}
}
public SHAKEDigest()
{
this(128);
}
public SHAKEDigest(CryptoServicePurpose purpose)
{
this(128, purpose);
}
/**
* Base constructor.
*
* @param bitStrength the security strength in bits of the XOF.
*/
public SHAKEDigest(int bitStrength)
{
super(checkBitLength(bitStrength), CryptoServicePurpose.ANY);
}
/**
* Base constructor.
*
* @param bitStrength the security strength in bits of the XOF.
* @param purpose the purpose of the digest will be used for.
*/
public SHAKEDigest(int bitStrength, CryptoServicePurpose purpose)
{
super(checkBitLength(bitStrength), purpose);
}
/**
* Clone constructor
*
* @param source the other digest to be copied.
*/
public SHAKEDigest(SHAKEDigest source)
{
super(source);
}
public String getAlgorithmName()
{
return "SHAKE" + fixedOutputLength;
}
public int getDigestSize()
{
return fixedOutputLength / 4;
}
public int doFinal(byte[] out, int outOff)
{
return doFinal(out, outOff, getDigestSize());
}
public int doFinal(byte[] out, int outOff, int outLen)
{
int length = doOutput(out, outOff, outLen);
reset();
return length;
}
public int doOutput(byte[] out, int outOff, int outLen)
{
if (!squeezing)
{
absorbBits(0x0F, 4);
}
squeeze(out, outOff, ((long)outLen) * 8);
return outLen;
}
/*
* TODO Possible API change to support partial-byte suffixes.
*/
protected int doFinal(byte[] out, int outOff, byte partialByte, int partialBits)
{
return doFinal(out, outOff, getDigestSize(), partialByte, partialBits);
}
/*
* TODO Possible API change to support partial-byte suffixes.
*/
protected int doFinal(byte[] out, int outOff, int outLen, byte partialByte, int partialBits)
{
if (partialBits < 0 || partialBits > 7)
{
throw new IllegalArgumentException("'partialBits' must be in the range [0,7]");
}
int finalInput = (partialByte & ((1 << partialBits) - 1)) | (0x0F << partialBits);
int finalBits = partialBits + 4;
if (finalBits >= 8)
{
absorb((byte)finalInput);
finalBits -= 8;
finalInput >>>= 8;
}
if (finalBits > 0)
{
absorbBits(finalInput, finalBits);
}
squeeze(out, outOff, ((long)outLen) * 8);
reset();
return outLen;
}
protected CryptoServiceProperties cryptoServiceProperties()
{
return null;
}
}

View File

@ -0,0 +1,4 @@
/**
* Message digest classes.
*/
package org.bouncycastle.crypto.digests;

View File

@ -0,0 +1,4 @@
/**
* Base classes for the lightweight API.
*/
package org.bouncycastle.crypto;

View File

@ -0,0 +1,20 @@
package org.bouncycastle.crypto.params;
import org.bouncycastle.crypto.CipherParameters;
public class AsymmetricKeyParameter
implements CipherParameters
{
boolean privateKey;
public AsymmetricKeyParameter(
boolean privateKey)
{
this.privateKey = privateKey;
}
public boolean isPrivate()
{
return privateKey;
}
}

View File

@ -0,0 +1,49 @@
package org.bouncycastle.crypto.params;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.util.Util;
public class ParametersWithContext
implements CipherParameters
{
private CipherParameters parameters;
private byte[] context;
public ParametersWithContext(
CipherParameters parameters,
byte[] context)
{
if (context == null)
{
throw new NullPointerException("'context' cannot be null");
}
this.parameters = parameters;
this.context = Util.clone(context);
}
public void copyContextTo(byte[] buf, int off, int len)
{
if (context.length != len)
{
throw new IllegalArgumentException("len");
}
System.arraycopy(context, 0, buf, off, len);
}
public byte[] getContext()
{
return Util.clone(context);
}
public int getContextLength()
{
return context.length;
}
public CipherParameters getParameters()
{
return parameters;
}
}

View File

@ -0,0 +1,37 @@
package org.bouncycastle.crypto.params;
import java.security.SecureRandom;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
public class ParametersWithRandom
implements CipherParameters
{
private SecureRandom random;
private CipherParameters parameters;
public ParametersWithRandom(
CipherParameters parameters,
SecureRandom random)
{
this.random = CryptoServicesRegistrar.getSecureRandom(random);
this.parameters = parameters;
}
public ParametersWithRandom(
CipherParameters parameters)
{
this(parameters, null);
}
public SecureRandom getRandom()
{
return random;
}
public CipherParameters getParameters()
{
return parameters;
}
}

View File

@ -0,0 +1,4 @@
/**
* Classes for parameter objects for ciphers and generators.
*/
package org.bouncycastle.crypto.params;

View File

@ -0,0 +1,6 @@
/**
* This is a small portion of bouncycastle 1.80 for MLKEM, modified to reduce dependencies.
*
* @since 0.9.67
*/
package org.bouncycastle;

View File

@ -0,0 +1,8 @@
package org.bouncycastle.pqc.crypto;
import org.bouncycastle.crypto.CipherParameters;
public interface KEMParameters
extends CipherParameters
{
}

View File

@ -0,0 +1,84 @@
package org.bouncycastle.pqc.crypto.mlkem;
final class CBD
{
public static void mlkemCBD(Poly r, byte[] bytes, int eta)
{
long t, d;
int a, b;
switch (eta)
{
case 3:
for (int i = 0; i < MLKEMEngine.KyberN / 4; i++)
{
t = convertByteTo24BitUnsignedInt(bytes, 3 * i);
d = t & 0x00249249;
d = d + ((t >> 1) & 0x00249249);
d = d + ((t >> 2) & 0x00249249);
for (int j = 0; j < 4; j++)
{
a = (short)((d >> (6 * j + 0)) & 0x7);
b = (short)((d >> (6 * j + 3)) & 0x7);
// System.out.printf("a = %d, b = %d\n", a, b);
r.setCoeffIndex(4 * i + j, (short)(a - b));
}
}
break;
default:
// Only for Kyber512 where eta = 2
for (int i = 0; i < MLKEMEngine.KyberN / 8; i++)
{
t = convertByteTo32BitUnsignedInt(bytes, 4 * i); // ? Problem
d = t & 0x55555555;
d = d + ((t >> 1) & 0x55555555);
for (int j = 0; j < 8; j++)
{
a = (short)((d >> (4 * j + 0)) & 0x3);
b = (short)((d >> (4 * j + eta)) & 0x3);
r.setCoeffIndex(8 * i + j, (short)(a - b));
}
}
}
}
/**
* Converts an Array of Bytes to a 32-bit Unsigned Integer
* Returns a 32-bit unsigned integer as a long
*
* @param x
* @return
*/
private static long convertByteTo32BitUnsignedInt(byte[] x, int offset)
{
// Convert first byte to an unsigned integer
// byte x & 0xFF allows us to grab the last 8 bits
long r = (long)(x[offset] & 0xFF);
// Perform the same operation then left bit shift to store the next 8 bits without
// altering the previous bits
r = r | (long)((long)(x[offset + 1] & 0xFF) << 8);
r = r | (long)((long)(x[offset + 2] & 0xFF) << 16);
r = r | (long)((long)(x[offset + 3] & 0xFF) << 24);
return r;
}
/**
* Converts an Array of Bytes to a 24-bit Unsigned Integer
* Returns a 24-bit unsigned integer as a long from byte x
*
* @param x
* @return
*/
private static long convertByteTo24BitUnsignedInt(byte[] x, int offset)
{
// Refer to convertByteTo32-BitUnsignedInt for explanation
long r = (long)(x[offset] & 0xFF);
r = r | (long)((long)(x[offset + 1] & 0xFF) << 8);
r = r | (long)((long)(x[offset + 2] & 0xFF) << 16);
return r;
}
}

View File

@ -0,0 +1,327 @@
package org.bouncycastle.pqc.crypto.mlkem;
import java.security.SecureRandom;
import java.util.Arrays;
import org.bouncycastle.util.Util;
class MLKEMEngine
{
private SecureRandom random;
private MLKEMIndCpa indCpa;
// constant parameters
public final static int KyberN = 256;
public final static int KyberQ = 3329;
public final static int KyberQinv = 62209;
public final static int KyberSymBytes = 32; // Number of bytes for Hashes and Seeds
private final static int KyberSharedSecretBytes = 32; // Number of Bytes for Shared Secret
public final static int KyberPolyBytes = 384;
private final static int KyberEta2 = 2;
private final static int KyberIndCpaMsgBytes = KyberSymBytes;
// parameters for Kyber{k}
private final int KyberK;
private final int KyberPolyVecBytes;
private final int KyberPolyCompressedBytes;
private final int KyberPolyVecCompressedBytes;
private final int KyberEta1;
private final int KyberIndCpaPublicKeyBytes;
private final int KyberIndCpaSecretKeyBytes;
private final int KyberIndCpaBytes;
private final int KyberPublicKeyBytes;
private final int KyberSecretKeyBytes;
private final int KyberCipherTextBytes;
// Crypto
private final int CryptoBytes;
private final int CryptoSecretKeyBytes;
private final int CryptoPublicKeyBytes;
private final int CryptoCipherTextBytes;
private final int sessionKeyLength;
private final Symmetric symmetric;
public Symmetric getSymmetric()
{
return symmetric;
}
public static int getKyberEta2()
{
return KyberEta2;
}
public static int getKyberIndCpaMsgBytes()
{
return KyberIndCpaMsgBytes;
}
public int getCryptoCipherTextBytes()
{
return CryptoCipherTextBytes;
}
public int getCryptoPublicKeyBytes()
{
return CryptoPublicKeyBytes;
}
public int getCryptoSecretKeyBytes()
{
return CryptoSecretKeyBytes;
}
public int getCryptoBytes()
{
return CryptoBytes;
}
public int getKyberCipherTextBytes()
{
return KyberCipherTextBytes;
}
public int getKyberSecretKeyBytes()
{
return KyberSecretKeyBytes;
}
public int getKyberIndCpaPublicKeyBytes()
{
return KyberIndCpaPublicKeyBytes;
}
public int getKyberIndCpaSecretKeyBytes()
{
return KyberIndCpaSecretKeyBytes;
}
public int getKyberIndCpaBytes()
{
return KyberIndCpaBytes;
}
public int getKyberPublicKeyBytes()
{
return KyberPublicKeyBytes;
}
public int getKyberPolyCompressedBytes()
{
return KyberPolyCompressedBytes;
}
public int getKyberK()
{
return KyberK;
}
public int getKyberPolyVecBytes()
{
return KyberPolyVecBytes;
}
public int getKyberPolyVecCompressedBytes()
{
return KyberPolyVecCompressedBytes;
}
public int getKyberEta1()
{
return KyberEta1;
}
public MLKEMEngine(int k)
{
this.KyberK = k;
switch (k)
{
case 2:
KyberEta1 = 3;
KyberPolyCompressedBytes = 128;
KyberPolyVecCompressedBytes = k * 320;
sessionKeyLength = 32;
break;
case 3:
KyberEta1 = 2;
KyberPolyCompressedBytes = 128;
KyberPolyVecCompressedBytes = k * 320;
sessionKeyLength = 32;
break;
case 4:
KyberEta1 = 2;
KyberPolyCompressedBytes = 160;
KyberPolyVecCompressedBytes = k * 352;
sessionKeyLength = 32;
break;
default:
throw new IllegalArgumentException("K: " + k + " is not supported for Crystals Kyber");
}
this.KyberPolyVecBytes = k * KyberPolyBytes;
this.KyberIndCpaPublicKeyBytes = KyberPolyVecBytes + KyberSymBytes;
this.KyberIndCpaSecretKeyBytes = KyberPolyVecBytes;
this.KyberIndCpaBytes = KyberPolyVecCompressedBytes + KyberPolyCompressedBytes;
this.KyberPublicKeyBytes = KyberIndCpaPublicKeyBytes;
this.KyberSecretKeyBytes = KyberIndCpaSecretKeyBytes + KyberIndCpaPublicKeyBytes + 2 * KyberSymBytes;
this.KyberCipherTextBytes = KyberIndCpaBytes;
// Define Crypto Params
this.CryptoBytes = KyberSharedSecretBytes;
this.CryptoSecretKeyBytes = KyberSecretKeyBytes;
this.CryptoPublicKeyBytes = KyberPublicKeyBytes;
this.CryptoCipherTextBytes = KyberCipherTextBytes;
this.symmetric = new Symmetric.ShakeSymmetric();
this.indCpa = new MLKEMIndCpa(this);
}
public void init(SecureRandom random)
{
this.random = random;
}
public byte[][] generateKemKeyPair()
{
byte[] d = new byte[KyberSymBytes];
byte[] z = new byte[KyberSymBytes];
random.nextBytes(d);
random.nextBytes(z);
return generateKemKeyPairInternal(d, z);
}
//Internal functions are deterministic. No randomness is sampled inside them
public byte[][] generateKemKeyPairInternal(byte[] d, byte[] z)
{
byte[][] indCpaKeyPair = indCpa.generateKeyPair(d);
byte[] s = new byte[KyberIndCpaSecretKeyBytes];
System.arraycopy(indCpaKeyPair[1], 0, s, 0, KyberIndCpaSecretKeyBytes);
byte[] hashedPublicKey = new byte[32];
symmetric.hash_h(hashedPublicKey, indCpaKeyPair[0], 0);
byte[] outputPublicKey = new byte[KyberIndCpaPublicKeyBytes];
System.arraycopy(indCpaKeyPair[0], 0, outputPublicKey, 0, KyberIndCpaPublicKeyBytes);
return new byte[][]
{
Arrays.copyOfRange(outputPublicKey, 0, outputPublicKey.length - 32),
Arrays.copyOfRange(outputPublicKey, outputPublicKey.length - 32, outputPublicKey.length),
s,
hashedPublicKey,
z,
Util.concatenate(d, z)
};
}
public byte[][] kemEncryptInternal(byte[] publicKeyInput, byte[] randBytes)
{
byte[] outputCipherText;
byte[] buf = new byte[2 * KyberSymBytes];
byte[] kr = new byte[2 * KyberSymBytes];
System.arraycopy(randBytes, 0, buf, 0, KyberSymBytes);
// SHA3-256 Public Key
symmetric.hash_h(buf, publicKeyInput, KyberSymBytes);
// SHA3-512( SHA3-256(RandBytes) || SHA3-256(PublicKey) )
symmetric.hash_g(kr, buf);
// IndCpa Encryption
outputCipherText = indCpa.encrypt(publicKeyInput, Arrays.copyOfRange(buf, 0, KyberSymBytes), Arrays.copyOfRange(kr, 32, kr.length));
byte[] outputSharedSecret = new byte[sessionKeyLength];
System.arraycopy(kr, 0, outputSharedSecret, 0, outputSharedSecret.length);
byte[][] outBuf = new byte[2][];
outBuf[0] = outputSharedSecret;
outBuf[1] = outputCipherText;
return outBuf;
}
public byte[] kemDecryptInternal(byte[] secretKey, byte[] cipherText)
{
byte[] buf = new byte[2 * KyberSymBytes],
kr = new byte[2 * KyberSymBytes];
byte[] publicKey = Arrays.copyOfRange(secretKey, KyberIndCpaSecretKeyBytes, secretKey.length);
System.arraycopy(indCpa.decrypt(secretKey, cipherText), 0, buf, 0, KyberSymBytes);
System.arraycopy(secretKey, KyberSecretKeyBytes - 2 * KyberSymBytes, buf, KyberSymBytes, KyberSymBytes);
symmetric.hash_g(kr, buf);
byte[] implicit_rejection = new byte[KyberSymBytes + KyberCipherTextBytes];
System.arraycopy(secretKey, KyberSecretKeyBytes - KyberSymBytes, implicit_rejection, 0, KyberSymBytes);
System.arraycopy(cipherText, 0, implicit_rejection, KyberSymBytes, KyberCipherTextBytes);
symmetric.kdf(implicit_rejection, implicit_rejection ); // J(z||c)
byte[] cmp = indCpa.encrypt(publicKey, Arrays.copyOfRange(buf, 0, KyberSymBytes), Arrays.copyOfRange(kr, KyberSymBytes, kr.length));
boolean fail = !(Util.constantTimeAreEqual(cipherText, cmp));
cmov(kr, implicit_rejection, KyberSymBytes, fail);
return Arrays.copyOfRange(kr, 0, sessionKeyLength);
}
public byte[][] kemEncrypt(byte[] publicKeyInput, byte[] randBytes)
{
//TODO: do input validation elsewhere?
// Input validation (6.2 ML-KEM Encaps)
// Type Check
if (publicKeyInput.length != KyberIndCpaPublicKeyBytes)
{
throw new IllegalArgumentException("Input validation Error: Type check failed for ml-kem encapsulation");
}
// Modulus Check
PolyVec polyVec = new PolyVec(this);
byte[] seed = indCpa.unpackPublicKey(polyVec, publicKeyInput);
byte[] ek = indCpa.packPublicKey(polyVec, seed);
if (!Arrays.equals(ek, publicKeyInput))
{
throw new IllegalArgumentException("Input validation: Modulus check failed for ml-kem encapsulation");
}
return kemEncryptInternal(publicKeyInput, randBytes);
}
public byte[] kemDecrypt(byte[] secretKey, byte[] cipherText)
{
//TODO: do input validation
return kemDecryptInternal(secretKey, cipherText);
}
private void cmov(byte[] r, byte[] x, int xlen, boolean b)
{
if (b)
{
System.arraycopy(x, 0, r, 0, xlen);
}
else
{
System.arraycopy(r, 0, r, 0, xlen);
}
}
public void getRandomBytes(byte[] buf)
{
this.random.nextBytes(buf);
}
}

View File

@ -0,0 +1,28 @@
package org.bouncycastle.pqc.crypto.mlkem;
public class MLKEMExtractor
{
private final MLKEMPrivateKeyParameters privateKey;
private final MLKEMEngine engine;
public MLKEMExtractor(MLKEMPrivateKeyParameters privateKey)
{
if (privateKey == null)
{
throw new NullPointerException("'privateKey' cannot be null");
}
this.privateKey = privateKey;
this.engine = privateKey.getParameters().getEngine();
}
public byte[] extractSecret(byte[] encapsulation)
{
return engine.kemDecrypt(privateKey.getEncoded(), encapsulation);
}
public int getEncapsulationLength()
{
return engine.getCryptoCipherTextBytes();
}
}

View File

@ -0,0 +1,40 @@
package org.bouncycastle.pqc.crypto.mlkem;
import java.security.SecureRandom;
import org.bouncycastle.crypto.SecretWithEncapsulation;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.pqc.crypto.util.SecretWithEncapsulationImpl;
public class MLKEMGenerator
{
// the source of randomness
private final SecureRandom sr;
public MLKEMGenerator(SecureRandom random)
{
this.sr = random;
}
public SecretWithEncapsulation generateEncapsulated(AsymmetricKeyParameter recipientKey)
{
MLKEMPublicKeyParameters key = (MLKEMPublicKeyParameters)recipientKey;
MLKEMEngine engine = key.getParameters().getEngine();
engine.init(sr);
byte[] randBytes = new byte[32];
engine.getRandomBytes(randBytes);
byte[][] kemEncrypt = engine.kemEncrypt(key.getEncoded(), randBytes);
return new SecretWithEncapsulationImpl(kemEncrypt[0], kemEncrypt[1]);
}
public SecretWithEncapsulation internalGenerateEncapsulated(AsymmetricKeyParameter recipientKey, byte[] randBytes)
{
MLKEMPublicKeyParameters key = (MLKEMPublicKeyParameters)recipientKey;
MLKEMEngine engine = key.getParameters().getEngine();
engine.init(sr);
byte[][] kemEncrypt = engine.kemEncryptInternal(key.getEncoded(), randBytes);
return new SecretWithEncapsulationImpl(kemEncrypt[0], kemEncrypt[1]);
}
}

View File

@ -0,0 +1,446 @@
package org.bouncycastle.pqc.crypto.mlkem;
import java.util.Arrays;
import org.bouncycastle.util.Util;
class MLKEMIndCpa
{
private MLKEMEngine engine;
private int kyberK;
private int indCpaPublicKeyBytes;
private int polyVecBytes;
private int indCpaBytes;
private int polyVecCompressedBytes;
private int polyCompressedBytes;
private Symmetric symmetric;
public MLKEMIndCpa(MLKEMEngine engine)
{
this.engine = engine;
this.kyberK = engine.getKyberK();
this.indCpaPublicKeyBytes = engine.getKyberPublicKeyBytes();
this.polyVecBytes = engine.getKyberPolyVecBytes();
this.indCpaBytes = engine.getKyberIndCpaBytes();
this.polyVecCompressedBytes = engine.getKyberPolyVecCompressedBytes();
this.polyCompressedBytes = engine.getKyberPolyCompressedBytes();
this.symmetric = engine.getSymmetric();
KyberGenerateMatrixNBlocks =
(
(
12 * MLKEMEngine.KyberN
/ 8 * (1 << 12)
/ MLKEMEngine.KyberQ + symmetric.xofBlockBytes
)
/ symmetric.xofBlockBytes
);
}
/**
* Generates IndCpa Key Pair
*
* @return KeyPair where each key is represented as bytes
*/
byte[][] generateKeyPair(byte[] d)
{
PolyVec secretKey = new PolyVec(engine),
publicKey = new PolyVec(engine),
e = new PolyVec(engine);
// (p, sigma) <- G(d || k)
byte[] buf = new byte[64];
symmetric.hash_g(buf, Util.append(d, (byte)kyberK));
byte[] publicSeed = new byte[32]; // p in docs
byte[] noiseSeed = new byte[32]; // sigma in docs
System.arraycopy(buf, 0, publicSeed, 0, 32);
System.arraycopy(buf, 32, noiseSeed, 0, 32);
byte count = (byte)0;
// Helper.printByteArray(buf);
PolyVec[] aMatrix = new PolyVec[kyberK];
int i;
for (i = 0; i < kyberK; i++)
{
aMatrix[i] = new PolyVec(engine);
}
generateMatrix(aMatrix, publicSeed, false);
// System.out.println("aMatrix = ");
// for(i = 0; i < kyberK; i++) {
// System.out.print("[");
// for (int j = 0; j < kyberK; j++) {
// System.out.print("[");
// for (int k = 0; k < KyberEngine.KyberN; k++) {
// System.out.printf("%d ,", aMatrix[i].getVectorIndex(j).getCoeffIndex(k));
// }
// System.out.print("], \n");
// }
// System.out.print("]\n");
// }
for (i = 0; i < kyberK; i++)
{
secretKey.getVectorIndex(i).getEta1Noise(noiseSeed, count);
// System.out.print("SecretKeyPolyVec["+i+"] = [");
// for (int j =0; j < KyberEngine.KyberN; j++) {
// System.out.print(secretKey.getVectorIndex(i).getCoeffIndex(j) + ", ");
// }
// System.out.println("]");
count = (byte)(count + (byte)1);
}
for (i = 0; i < kyberK; i++)
{
e.getVectorIndex(i).getEta1Noise(noiseSeed, count);
count = (byte)(count + (byte)1);
}
secretKey.polyVecNtt();
// System.out.print("SecretKeyPolyVec = [");
// for (i = 0; i < kyberK; i++) {
// System.out.print("[");
// for (int j =0; j < KyberEngine.KyberN; j++) {
// System.out.print(secretKey.getVectorIndex(i).getCoeffIndex(j) + ", ");
// }
// System.out.println("],");
// }
// System.out.println("]");
e.polyVecNtt();
for (i = 0; i < kyberK; i++)
{
PolyVec.pointwiseAccountMontgomery(publicKey.getVectorIndex(i), aMatrix[i], secretKey, engine);
publicKey.getVectorIndex(i).convertToMont();
}
// System.out.print("PublicKey PolyVec = [");
// Helper.printPolyVec(publicKey, kyberK);
publicKey.addPoly(e);
publicKey.reducePoly();
return new byte[][]{packPublicKey(publicKey, publicSeed), packSecretKey(secretKey)};
}
public byte[] encrypt(byte[] publicKeyInput, byte[] msg, byte[] coins)
{
int i;
byte[] seed;
byte nonce = (byte)0;
PolyVec sp = new PolyVec(engine),
publicKeyPolyVec = new PolyVec(engine),
errorPolyVector = new PolyVec(engine),
bp = new PolyVec(engine);
PolyVec[] aMatrixTranspose = new PolyVec[engine.getKyberK()];
Poly errorPoly = new Poly(engine),
v = new Poly(engine),
k = new Poly(engine);
// System.out.print("publickeyinput = ");
// Helper.printByteArray(publicKeyInput);
// System.out.println();
seed = unpackPublicKey(publicKeyPolyVec, publicKeyInput);
// System.out.print("publickeyPolyVec = [");
// for (i = 0; i < kyberK; i++) {
// Helper.printShortArray(publicKeyPolyVec.getVectorIndex(i).getCoeffs());
// System.out.print("], \n");
// }
// System.out.println("]");
// System.out.print("seed = ");
// Helper.printByteArray(seed);
// System.out.println();
k.fromMsg(msg);
for (i = 0; i < kyberK; i++)
{
aMatrixTranspose[i] = new PolyVec(engine);
}
generateMatrix(aMatrixTranspose, seed, true);
// System.out.print("matrix transposed = ");
// for (i = 0; i < kyberK; i++) {
// System.out.print("[");
// for(int j = 0; j < kyberK; j++) {
// System.out.print("[");
// for (int l = 0; l < 256; l++) {
// System.out.printf("%d ,", aMatrixTranspose[i].getVectorIndex(j).getCoeffIndex(l));
// }
// System.out.print("] ,\n");
// }
// System.out.println("] ,");
// }
for (i = 0; i < kyberK; i++)
{
sp.getVectorIndex(i).getEta1Noise(coins, nonce);
nonce = (byte)(nonce + (byte)1);
}
for (i = 0; i < kyberK; i++)
{
errorPolyVector.getVectorIndex(i).getEta2Noise(coins, nonce);
nonce = (byte)(nonce + (byte)1);
}
errorPoly.getEta2Noise(coins, nonce);
sp.polyVecNtt();
// System.out.print("sp = [");
// for (i = 0; i < kyberK; i++) {
// Helper.printShortArray(sp.getVectorIndex(i).getCoeffs());
// System.out.print("], \n");
// }
// System.out.println("]");
// System.out.print("sp = [");
// for (i = 0; i < kyberK; i++) {
// Helper.printShortArray(sp.getVectorIndex(i).getCoeffs());
// System.out.print("], \n");
// }
// System.out.println("]");
for (i = 0; i < kyberK; i++)
{
PolyVec.pointwiseAccountMontgomery(bp.getVectorIndex(i), aMatrixTranspose[i], sp, engine);
}
// System.out.print("bp = [");
// for (i = 0; i < kyberK; i++) {
// Helper.printShortArray(bp.getVectorIndex(i).getCoeffs());
// System.out.print("], \n");
// }
// System.out.println("]");
PolyVec.pointwiseAccountMontgomery(v, publicKeyPolyVec, sp, engine);
bp.polyVecInverseNttToMont();
v.polyInverseNttToMont();
bp.addPoly(errorPolyVector);
v.addCoeffs(errorPoly);
v.addCoeffs(k);
bp.reducePoly();
v.reduce();
// System.out.print("bp = [");
// for (i = 0; i < kyberK; i++) {
// Helper.printShortArray(bp.getVectorIndex(i).getCoeffs());
// System.out.print("], \n");
// }
// System.out.println("]");
// System.out.print("v = ");
// Helper.printShortArray(v.getCoeffs());
// System.out.println();
byte[] outputCipherText = packCipherText(bp, v);
return outputCipherText;
}
private byte[] packCipherText(PolyVec b, Poly v)
{
byte[] outBuf = new byte[indCpaBytes];
System.arraycopy(b.compressPolyVec(), 0, outBuf, 0, polyVecCompressedBytes);
System.arraycopy(v.compressPoly(), 0, outBuf, polyVecCompressedBytes, polyCompressedBytes);
// System.out.print("outBuf = [");
// Helper.printByteArray(outBuf);
return outBuf;
}
private void unpackCipherText(PolyVec b, Poly v, byte[] cipherText)
{
byte[] compressedPolyVecCipherText = Arrays.copyOfRange(cipherText, 0, engine.getKyberPolyVecCompressedBytes());
b.decompressPolyVec(compressedPolyVecCipherText);
byte[] compressedPolyCipherText = Arrays.copyOfRange(cipherText, engine.getKyberPolyVecCompressedBytes(), cipherText.length);
v.decompressPoly(compressedPolyCipherText);
}
public byte[] packPublicKey(PolyVec publicKeyPolyVec, byte[] seed)
{
byte[] buf = new byte[indCpaPublicKeyBytes];
System.arraycopy(publicKeyPolyVec.toBytes(), 0, buf, 0, polyVecBytes);
System.arraycopy(seed, 0, buf, polyVecBytes, MLKEMEngine.KyberSymBytes);
return buf;
}
public byte[] unpackPublicKey(PolyVec publicKeyPolyVec, byte[] publicKey)
{
byte[] outputSeed = new byte[MLKEMEngine.KyberSymBytes];
publicKeyPolyVec.fromBytes(publicKey);
System.arraycopy(publicKey, polyVecBytes, outputSeed, 0, MLKEMEngine.KyberSymBytes);
return outputSeed;
}
public byte[] packSecretKey(PolyVec secretKeyPolyVec)
{
return secretKeyPolyVec.toBytes();
}
public void unpackSecretKey(PolyVec secretKeyPolyVec, byte[] secretKey)
{
secretKeyPolyVec.fromBytes(secretKey);
}
public final int KyberGenerateMatrixNBlocks;
public void generateMatrix(PolyVec[] aMatrix, byte[] seed, boolean transposed)
{
int i, j, k, ctr, off;
byte[] buf = new byte[KyberGenerateMatrixNBlocks * symmetric.xofBlockBytes + 2];
for (i = 0; i < kyberK; i++)
{
for (j = 0; j < kyberK; j++)
{
if (transposed)
{
symmetric.xofAbsorb(seed, (byte) i, (byte) j);
}
else
{
symmetric.xofAbsorb(seed, (byte) j, (byte) i);
}
symmetric.xofSqueezeBlocks(buf, 0, symmetric.xofBlockBytes * KyberGenerateMatrixNBlocks);
int buflen = KyberGenerateMatrixNBlocks * symmetric.xofBlockBytes;
ctr = rejectionSampling(aMatrix[i].getVectorIndex(j), 0, MLKEMEngine.KyberN, buf, buflen);
while (ctr < MLKEMEngine.KyberN)
{
off = buflen % 3;
for (k = 0; k < off; k++)
{
buf[k] = buf[buflen - off + k];
}
symmetric.xofSqueezeBlocks(buf, off, symmetric.xofBlockBytes * 2);
buflen = off + symmetric.xofBlockBytes;
// Error in code Section Unsure
ctr += rejectionSampling(aMatrix[i].getVectorIndex(j), ctr, MLKEMEngine.KyberN - ctr, buf, buflen);
}
}
}
}
private static int rejectionSampling(Poly outputBuffer, int coeffOff, int len, byte[] inpBuf, int inpBufLen)
{
int ctr, pos;
short val0, val1;
ctr = pos = 0;
while (ctr < len && pos + 3 <= inpBufLen)
{
val0 = (short)(((((short)(inpBuf[pos] & 0xFF)) >> 0) | (((short)(inpBuf[pos + 1] & 0xFF)) << 8)) & 0xFFF);
val1 = (short)(((((short)(inpBuf[pos + 1] & 0xFF)) >> 4) | (((short)(inpBuf[pos + 2] & 0xFF)) << 4)) & 0xFFF);
pos = pos + 3;
if (val0 < (short)MLKEMEngine.KyberQ)
{
outputBuffer.setCoeffIndex(coeffOff + ctr, (short)val0);
ctr++;
}
if (ctr < len && val1 < (short)MLKEMEngine.KyberQ)
{
outputBuffer.setCoeffIndex(coeffOff + ctr, (short)val1);
ctr++;
}
}
return ctr;
}
public byte[] decrypt(byte[] secretKey, byte[] cipherText)
{
byte[] outputMessage = new byte[MLKEMEngine.getKyberIndCpaMsgBytes()];
PolyVec bp = new PolyVec(engine), secretKeyPolyVec = new PolyVec(engine);
Poly v = new Poly(engine), mp = new Poly(engine);
unpackCipherText(bp, v, cipherText);
// System.out.print("bp = [");
// for (i = 0; i < kyberK; i++) {
// Helper.printShortArray(bp.getVectorIndex(i).getCoeffs());
// System.out.print("], \n");
// }
// System.out.println("]");
// System.out.print("v = ");
// Helper.printShortArray(v.getCoeffs());
// System.out.println();
unpackSecretKey(secretKeyPolyVec, secretKey);
// System.out.print("decrypt secretkey = ");;
// Helper.printByteArray(secretKey);
// System.out.print("SecretKeyPolyVec = [");
// for (i = 0; i < kyberK; i++) {
// System.out.print("[");
// for (int j =0; j < KyberEngine.KyberN; j++) {
// System.out.print(secretKeyPolyVec.getVectorIndex(i).getCoeffIndex(j) + ", ");
// }
// System.out.println("],");
// }
// System.out.println("]");
// System.out.print("bp before ntt = [");
// for (i = 0; i < kyberK; i++) {
// Helper.printShortArray(bp.getVectorIndex(i).getCoeffs());
// System.out.print("], \n");
// }
// System.out.println("]");
bp.polyVecNtt();
// System.out.print("bp after ntt = [");
// for (i = 0; i < kyberK; i++) {
// Helper.printShortArray(bp.getVectorIndex(i).getCoeffs());
// System.out.print("], \n");
// }
// System.out.println("]");
PolyVec.pointwiseAccountMontgomery(mp, secretKeyPolyVec, bp, engine);
mp.polyInverseNttToMont();
mp.polySubtract(v);
mp.reduce();
outputMessage = mp.toMsg();
return outputMessage;
}
}

View File

@ -0,0 +1,24 @@
package org.bouncycastle.pqc.crypto.mlkem;
import java.security.SecureRandom;
import org.bouncycastle.crypto.KeyGenerationParameters;
public class MLKEMKeyGenerationParameters
extends KeyGenerationParameters
{
private final MLKEMParameters params;
public MLKEMKeyGenerationParameters(
SecureRandom random,
MLKEMParameters mlkemParameters)
{
super(random, 256);
this.params = mlkemParameters;
}
public MLKEMParameters getParameters()
{
return params;
}
}

View File

@ -0,0 +1,55 @@
package org.bouncycastle.pqc.crypto.mlkem;
import java.security.SecureRandom;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.KeyGenerationParameters;
public class MLKEMKeyPairGenerator
{
private MLKEMParameters mlkemParams;
private SecureRandom random;
private void initialize(
KeyGenerationParameters param)
{
this.mlkemParams = ((MLKEMKeyGenerationParameters)param).getParameters();
this.random = param.getRandom();
}
private AsymmetricCipherKeyPair genKeyPair()
{
MLKEMEngine engine = mlkemParams.getEngine();
engine.init(random);
byte[][] keyPair = engine.generateKemKeyPair();
MLKEMPublicKeyParameters pubKey = new MLKEMPublicKeyParameters(mlkemParams, keyPair[0], keyPair[1]);
MLKEMPrivateKeyParameters privKey = new MLKEMPrivateKeyParameters(mlkemParams, keyPair[2], keyPair[3], keyPair[4], keyPair[0], keyPair[1], keyPair[5]);
return new AsymmetricCipherKeyPair(pubKey, privKey);
}
public void init(KeyGenerationParameters param)
{
this.initialize(param);
}
public AsymmetricCipherKeyPair generateKeyPair()
{
return genKeyPair();
}
public AsymmetricCipherKeyPair internalGenerateKeyPair(byte[] d, byte[] z)
{
byte[][] keyPair = mlkemParams.getEngine().generateKemKeyPairInternal(d, z);
MLKEMPublicKeyParameters pubKey = new MLKEMPublicKeyParameters(mlkemParams, keyPair[0], keyPair[1]);
MLKEMPrivateKeyParameters privKey = new MLKEMPrivateKeyParameters(mlkemParams, keyPair[2], keyPair[3], keyPair[4], keyPair[0], keyPair[1], keyPair[5]);
return new AsymmetricCipherKeyPair(pubKey, privKey);
}
}

View File

@ -0,0 +1,23 @@
package org.bouncycastle.pqc.crypto.mlkem;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
public class MLKEMKeyParameters
extends AsymmetricKeyParameter
{
private MLKEMParameters params;
public MLKEMKeyParameters(
boolean isPrivate,
MLKEMParameters params)
{
super(isPrivate);
this.params = params;
}
public MLKEMParameters getParameters()
{
return params;
}
}

View File

@ -0,0 +1,37 @@
package org.bouncycastle.pqc.crypto.mlkem;
import org.bouncycastle.pqc.crypto.KEMParameters;
public class MLKEMParameters
implements KEMParameters
{
public static final MLKEMParameters ml_kem_512 = new MLKEMParameters("ML-KEM-512", 2, 256);
public static final MLKEMParameters ml_kem_768 = new MLKEMParameters("ML-KEM-768", 3, 256);
public static final MLKEMParameters ml_kem_1024 = new MLKEMParameters("ML-KEM-1024", 4, 256);
private final String name;
private final int k;
private final int sessionKeySize;
private MLKEMParameters(String name, int k, int sessionKeySize)
{
this.name = name;
this.k = k;
this.sessionKeySize = sessionKeySize;
}
public String getName()
{
return name;
}
public MLKEMEngine getEngine()
{
return new MLKEMEngine(k);
}
public int getSessionKeySize()
{
return sessionKeySize;
}
}

View File

@ -0,0 +1,111 @@
package org.bouncycastle.pqc.crypto.mlkem;
import java.util.Arrays;
import org.bouncycastle.util.Util;
public class MLKEMPrivateKeyParameters
extends MLKEMKeyParameters
{
final byte[] s;
final byte[] hpk;
final byte[] nonce;
final byte[] t;
final byte[] rho;
final byte[] seed;
public MLKEMPrivateKeyParameters(MLKEMParameters params, byte[] s, byte[] hpk, byte[] nonce, byte[] t, byte[] rho)
{
this(params, s, hpk, nonce, t, rho, null);
}
public MLKEMPrivateKeyParameters(MLKEMParameters params, byte[] s, byte[] hpk, byte[] nonce, byte[] t, byte[] rho, byte[] seed)
{
super(true, params);
this.s = Util.clone(s);
this.hpk = Util.clone(hpk);
this.nonce = Util.clone(nonce);
this.t = Util.clone(t);
this.rho = Util.clone(rho);
this.seed = Util.clone(seed);
}
public MLKEMPrivateKeyParameters(MLKEMParameters params, byte[] encoding)
{
super(true, params);
MLKEMEngine eng = params.getEngine();
if (encoding.length == MLKEMEngine.KyberSymBytes * 2)
{
byte[][] keyData = eng.generateKemKeyPairInternal(
Arrays.copyOfRange(encoding, 0, MLKEMEngine.KyberSymBytes),
Arrays.copyOfRange(encoding, MLKEMEngine.KyberSymBytes, encoding.length));
this.s = keyData[2];
this.hpk = keyData[3];
this.nonce = keyData[4];
this.t = keyData[0];
this.rho = keyData[1];
this.seed = keyData[5];
}
else
{
int index = 0;
this.s = Arrays.copyOfRange(encoding, 0, eng.getKyberIndCpaSecretKeyBytes());
index += eng.getKyberIndCpaSecretKeyBytes();
this.t = Arrays.copyOfRange(encoding, index, index + eng.getKyberIndCpaPublicKeyBytes() - MLKEMEngine.KyberSymBytes);
index += eng.getKyberIndCpaPublicKeyBytes() - MLKEMEngine.KyberSymBytes;
this.rho = Arrays.copyOfRange(encoding, index, index + 32);
index += 32;
this.hpk = Arrays.copyOfRange(encoding, index, index + 32);
index += 32;
this.nonce = Arrays.copyOfRange(encoding, index, index + MLKEMEngine.KyberSymBytes);
this.seed = null;
}
}
public byte[] getEncoded()
{
return Util.concatenate(new byte[][]{ s, t, rho, hpk, nonce });
}
public byte[] getHPK()
{
return Util.clone(hpk);
}
public byte[] getNonce()
{
return Util.clone(nonce);
}
public byte[] getPublicKey()
{
return MLKEMPublicKeyParameters.getEncoded(t, rho);
}
public MLKEMPublicKeyParameters getPublicKeyParameters()
{
return new MLKEMPublicKeyParameters(getParameters(), t, rho);
}
public byte[] getRho()
{
return Util.clone(rho);
}
public byte[] getS()
{
return Util.clone(s);
}
public byte[] getT()
{
return Util.clone(t);
}
public byte[] getSeed()
{
return Util.clone(seed);
}
}

View File

@ -0,0 +1,46 @@
package org.bouncycastle.pqc.crypto.mlkem;
import java.util.Arrays;
import org.bouncycastle.util.Util;
public class MLKEMPublicKeyParameters
extends MLKEMKeyParameters
{
static byte[] getEncoded(byte[] t, byte[] rho)
{
return Util.concatenate(t, rho);
}
final byte[] t;
final byte[] rho;
public MLKEMPublicKeyParameters(MLKEMParameters params, byte[] t, byte[] rho)
{
super(false, params);
this.t = Util.clone(t);
this.rho = Util.clone(rho);
}
public MLKEMPublicKeyParameters(MLKEMParameters params, byte[] encoding)
{
super(false, params);
this.t = Arrays.copyOfRange(encoding, 0, encoding.length - MLKEMEngine.KyberSymBytes);
this.rho = Arrays.copyOfRange(encoding, encoding.length - MLKEMEngine.KyberSymBytes, encoding.length);
}
public byte[] getEncoded()
{
return getEncoded(t, rho);
}
public byte[] getRho()
{
return Util.clone(rho);
}
public byte[] getT()
{
return Util.clone(t);
}
}

View File

@ -0,0 +1,100 @@
package org.bouncycastle.pqc.crypto.mlkem;
class Ntt
{
public static final short[] nttZetas = new short[]{
2285, 2571, 2970, 1812, 1493, 1422, 287, 202, 3158, 622, 1577, 182, 962,
2127, 1855, 1468, 573, 2004, 264, 383, 2500, 1458, 1727, 3199, 2648, 1017,
732, 608, 1787, 411, 3124, 1758, 1223, 652, 2777, 1015, 2036, 1491, 3047,
1785, 516, 3321, 3009, 2663, 1711, 2167, 126, 1469, 2476, 3239, 3058, 830,
107, 1908, 3082, 2378, 2931, 961, 1821, 2604, 448, 2264, 677, 2054, 2226,
430, 555, 843, 2078, 871, 1550, 105, 422, 587, 177, 3094, 3038, 2869, 1574,
1653, 3083, 778, 1159, 3182, 2552, 1483, 2727, 1119, 1739, 644, 2457, 349,
418, 329, 3173, 3254, 817, 1097, 603, 610, 1322, 2044, 1864, 384, 2114, 3193,
1218, 1994, 2455, 220, 2142, 1670, 2144, 1799, 2051, 794, 1819, 2475, 2459,
478, 3221, 3021, 996, 991, 958, 1869, 1522, 1628};
public static final short[] nttZetasInv = new short[]{
1701, 1807, 1460, 2371, 2338, 2333, 308, 108, 2851, 870, 854, 1510, 2535,
1278, 1530, 1185, 1659, 1187, 3109, 874, 1335, 2111, 136, 1215, 2945, 1465,
1285, 2007, 2719, 2726, 2232, 2512, 75, 156, 3000, 2911, 2980, 872, 2685,
1590, 2210, 602, 1846, 777, 147, 2170, 2551, 246, 1676, 1755, 460, 291, 235,
3152, 2742, 2907, 3224, 1779, 2458, 1251, 2486, 2774, 2899, 1103, 1275, 2652,
1065, 2881, 725, 1508, 2368, 398, 951, 247, 1421, 3222, 2499, 271, 90, 853,
1860, 3203, 1162, 1618, 666, 320, 8, 2813, 1544, 282, 1838, 1293, 2314, 552,
2677, 2106, 1571, 205, 2918, 1542, 2721, 2597, 2312, 681, 130, 1602, 1871,
829, 2946, 3065, 1325, 2756, 1861, 1474, 1202, 2367, 3147, 1752, 2707, 171,
3127, 3042, 1907, 1836, 1517, 359, 758, 1441};
public static short[] ntt(short[] inp)
{
short[] r = new short[MLKEMEngine.KyberN];
System.arraycopy(inp, 0, r, 0, r.length);
int len, start, j, k;
short t, zeta;
k = 1;
for (len = 128; len >= 2; len >>= 1)
{
for (start = 0; start < 256; start = j + len)
{
zeta = nttZetas[k++];
for (j = start; j < start + len; ++j)
{
t = factorQMulMont(zeta, r[j + len]);
r[j + len] = (short)(r[j] - t);
r[j] = (short)(r[j] + t);
}
}
}
return r;
}
public static short[] invNtt(short[] inp)
{
short[] r = new short[MLKEMEngine.KyberN];
System.arraycopy(inp, 0, r, 0, MLKEMEngine.KyberN);
int len, start, j, k;
short t, zeta;
k = 0;
for (len = 2; len <= 128; len <<= 1)
{
for (start = 0; start < 256; start = j + len)
{
zeta = nttZetasInv[k++];
for (j = start; j < start + len; ++j)
{
t = r[j];
r[j] = Reduce.barretReduce((short)(t + r[j + len]));
r[j + len] = (short)(t - r[j + len]);
r[j + len] = factorQMulMont(zeta, r[j + len]);
}
}
}
for (j = 0; j < 256; ++j)
{
r[j] = factorQMulMont(r[j], Ntt.nttZetasInv[127]);
}
return r;
}
public static short factorQMulMont(short a, short b)
{
return Reduce.montgomeryReduce((int)(a * b));
}
public static void baseMult(Poly outPoly, int outIndex, short a0, short a1, short b0, short b1, short zeta)
{
short outVal0 = factorQMulMont(a1, b1);
outVal0 = factorQMulMont(outVal0, zeta);
outVal0 += factorQMulMont(a0, b0);
outPoly.setCoeffIndex(outIndex, outVal0);
short outVal1 = factorQMulMont(a0, b1);
outVal1 += factorQMulMont(a1, b0);
outPoly.setCoeffIndex(outIndex + 1, outVal1);
}
}

View File

@ -0,0 +1,354 @@
package org.bouncycastle.pqc.crypto.mlkem;
class Poly
{
private short[] coeffs;
private MLKEMEngine engine;
private int polyCompressedBytes;
private int eta1;
private int eta2;
private Symmetric symmetric;
public Poly(MLKEMEngine engine)
{
this.coeffs = new short[MLKEMEngine.KyberN];
this.engine = engine;
polyCompressedBytes = engine.getKyberPolyCompressedBytes();
this.eta1 = engine.getKyberEta1();
this.eta2 = MLKEMEngine.getKyberEta2();
this.symmetric = engine.getSymmetric();
}
public short getCoeffIndex(int i)
{
return this.coeffs[i];
}
public short[] getCoeffs()
{
return this.coeffs;
}
public void setCoeffIndex(int i, short val)
{
this.coeffs[i] = val;
}
public void setCoeffs(short[] coeffs)
{
this.coeffs = coeffs;
}
public void polyNtt()
{
this.setCoeffs(Ntt.ntt(this.getCoeffs()));
this.reduce();
}
public void polyInverseNttToMont()
{
this.setCoeffs(Ntt.invNtt(this.getCoeffs()));
}
public void reduce()
{
int i;
for (i = 0; i < MLKEMEngine.KyberN; i++)
{
this.setCoeffIndex(i, Reduce.barretReduce(this.getCoeffIndex(i)));
}
}
public static void baseMultMontgomery(Poly r, Poly a, Poly b)
{
int i;
for (i = 0; i < MLKEMEngine.KyberN / 4; i++)
{
Ntt.baseMult(r, 4 * i,
a.getCoeffIndex(4 * i), a.getCoeffIndex(4 * i + 1),
b.getCoeffIndex(4 * i), b.getCoeffIndex(4 * i + 1),
Ntt.nttZetas[64 + i]);
Ntt.baseMult(r, 4 * i + 2,
a.getCoeffIndex(4 * i + 2), a.getCoeffIndex(4 * i + 3),
b.getCoeffIndex(4 * i + 2), b.getCoeffIndex(4 * i + 3),
(short)(-1 * Ntt.nttZetas[64 + i]));
}
}
public void addCoeffs(Poly b)
{
int i;
for (i = 0; i < MLKEMEngine.KyberN; i++)
{
this.setCoeffIndex(i, (short)(this.getCoeffIndex(i) + b.getCoeffIndex(i)));
}
}
public void convertToMont()
{
int i;
final short f = (short)(((long)1 << 32) % MLKEMEngine.KyberQ);
for (i = 0; i < MLKEMEngine.KyberN; i++)
{
this.setCoeffIndex(i, Reduce.montgomeryReduce(this.getCoeffIndex(i) * f));
}
}
public byte[] compressPoly()
{
int i, j;
byte[] t = new byte[8];
byte[] r = new byte[polyCompressedBytes];
int count = 0;
this.conditionalSubQ();
// System.out.print("v = [");
// Helper.printShortArray(this.coeffs);
// System.out.print("]\n");
if (polyCompressedBytes == 128)
{
for (i = 0; i < MLKEMEngine.KyberN / 8; i++)
{
for (j = 0; j < 8; j++)
{
/*t[j] =
(byte)((((((short)this.getCoeffIndex(8 * i + j)) << 4)
+
(KyberEngine.KyberQ / 2)
) / KyberEngine.KyberQ)
& 15);*/
// Fix for KyberSlash2: division by KyberQ above is not
// constant time.
int t_j = this.getCoeffIndex(8 * i + j);
t_j <<= 4;
t_j += 1665;
t_j *= 80635;
t_j >>= 28;
t_j &= 15;
t[j] = (byte)t_j;
}
r[count + 0] = (byte)(t[0] | (t[1] << 4));
r[count + 1] = (byte)(t[2] | (t[3] << 4));
r[count + 2] = (byte)(t[4] | (t[5] << 4));
r[count + 3] = (byte)(t[6] | (t[7] << 4));
count += 4;
}
}
else if (polyCompressedBytes == 160)
{
for (i = 0; i < MLKEMEngine.KyberN / 8; i++)
{
for (j = 0; j < 8; j++)
{
/*t[j] =
(byte)(((((this.getCoeffIndex(8 * i + j) << 5))
+
(KyberEngine.KyberQ / 2)
) / KyberEngine.KyberQ
) & 31
);*/
// Fix for KyberSlash2: division by KyberQ above is not
// constant time.
int t_j = this.getCoeffIndex(8 * i + j);
t_j <<= 5;
t_j += 1664;
t_j *= 40318;
t_j >>= 27;
t_j &= 31;
t[j] = (byte)t_j;
}
r[count + 0] = (byte)((t[0] >> 0) | (t[1] << 5));
r[count + 1] = (byte)((t[1] >> 3) | (t[2] << 2) | (t[3] << 7));
r[count + 2] = (byte)((t[3] >> 1) | (t[4] << 4));
r[count + 3] = (byte)((t[4] >> 4) | (t[5] << 1) | (t[6] << 6));
r[count + 4] = (byte)((t[6] >> 2) | (t[7] << 3));
count += 5;
}
}
else
{
throw new RuntimeException("PolyCompressedBytes is neither 128 or 160!");
}
// System.out.print("r = ");
// Helper.printByteArray(r);
// System.out.println();
return r;
}
public void decompressPoly(byte[] compressedPolyCipherText)
{
int i, count = 0;
if (engine.getKyberPolyCompressedBytes() == 128)
{
for (i = 0; i < MLKEMEngine.KyberN / 2; i++)
{
this.setCoeffIndex(2 * i + 0, (short)((((short)((compressedPolyCipherText[count] & 0xFF) & 15) * MLKEMEngine.KyberQ) + 8) >> 4));
this.setCoeffIndex(2 * i + 1, (short)((((short)((compressedPolyCipherText[count] & 0xFF) >> 4) * MLKEMEngine.KyberQ) + 8) >> 4));
count += 1;
}
}
else if (engine.getKyberPolyCompressedBytes() == 160)
{
int j;
byte[] t = new byte[8];
for (i = 0; i < MLKEMEngine.KyberN / 8; i++)
{
t[0] = (byte)((compressedPolyCipherText[count + 0] & 0xFF) >> 0);
t[1] = (byte)(((compressedPolyCipherText[count + 0] & 0xFF) >> 5) | ((compressedPolyCipherText[count + 1] & 0xFF) << 3));
t[2] = (byte)((compressedPolyCipherText[count + 1] & 0xFF) >> 2);
t[3] = (byte)(((compressedPolyCipherText[count + 1] & 0xFF) >> 7) | ((compressedPolyCipherText[count + 2] & 0xFF) << 1));
t[4] = (byte)(((compressedPolyCipherText[count + 2] & 0xFF) >> 4) | ((compressedPolyCipherText[count + 3] & 0xFF) << 4));
t[5] = (byte)((compressedPolyCipherText[count + 3] & 0xFF) >> 1);
t[6] = (byte)(((compressedPolyCipherText[count + 3] & 0xFF) >> 6) | ((compressedPolyCipherText[count + 4] & 0xFF) << 2));
t[7] = (byte)((compressedPolyCipherText[count + 4] & 0xFF) >> 3);
count += 5;
for (j = 0; j < 8; j++)
{
this.setCoeffIndex(8 * i + j, (short)(((t[j] & 31) * MLKEMEngine.KyberQ + 16) >> 5));
}
}
}
else
{
throw new RuntimeException("PolyCompressedBytes is neither 128 or 160!");
}
}
public byte[] toBytes()
{
byte[] r = new byte[MLKEMEngine.KyberPolyBytes];
short t0, t1;
this.conditionalSubQ();
for (int i = 0; i < MLKEMEngine.KyberN / 2; i++)
{
t0 = this.getCoeffIndex(2 * i);
t1 = this.getCoeffIndex(2 * i + 1);
r[3 * i] = (byte)(t0 >> 0);
r[3 * i + 1] = (byte)((t0 >> 8) | (t1 << 4));
r[3 * i + 2] = (byte)(t1 >> 4);
}
return r;
}
public void fromBytes(byte[] inpBytes)
{
int i;
for (i = 0; i < MLKEMEngine.KyberN / 2; i++)
{
this.setCoeffIndex(2 * i, (short)(
(
((inpBytes[3 * i + 0] & 0xFF) >> 0)
| ((inpBytes[3 * i + 1] & 0xFF) << 8)
) & 0xFFF)
);
this.setCoeffIndex(2 * i + 1, (short)(
(
((inpBytes[3 * i + 1] & 0xFF) >> 4)
| (long)((inpBytes[3 * i + 2] & 0xFF) << 4)
) & 0xFFF)
);
}
}
public byte[] toMsg()
{
int LOWER = MLKEMEngine.KyberQ >>> 2;
int UPPER = MLKEMEngine.KyberQ - LOWER;
byte[] outMsg = new byte[MLKEMEngine.getKyberIndCpaMsgBytes()];
this.conditionalSubQ();
for (int i = 0; i < MLKEMEngine.KyberN / 8; i++)
{
outMsg[i] = 0;
for (int j = 0; j < 8; j++)
{
int c_j = this.getCoeffIndex(8 * i + j);
// KyberSlash: division by Q is not constant time.
// int t = (((c_j << 1) + (KyberEngine.KyberQ / 2)) / KyberEngine.KyberQ) & 1;
int t = ((LOWER - c_j) & (c_j - UPPER)) >>> 31;
outMsg[i] |= (byte)(t << j);
}
}
return outMsg;
}
public void fromMsg(byte[] msg)
{
int i, j;
short mask;
if (msg.length != MLKEMEngine.KyberN / 8)
{
throw new RuntimeException("KYBER_INDCPA_MSGBYTES must be equal to KYBER_N/8 bytes!");
}
for (i = 0; i < MLKEMEngine.KyberN / 8; i++)
{
for (j = 0; j < 8; j++)
{
mask = (short)((-1) * (short)(((msg[i] & 0xFF) >> j) & 1));
this.setCoeffIndex(8 * i + j, (short)(mask & (short)((MLKEMEngine.KyberQ + 1) / 2)));
}
}
}
public void conditionalSubQ()
{
int i;
for (i = 0; i < MLKEMEngine.KyberN; i++)
{
this.setCoeffIndex(i, Reduce.conditionalSubQ(this.getCoeffIndex(i)));
}
}
public void getEta1Noise(byte[] seed, byte nonce)
{
byte[] buf = new byte[MLKEMEngine.KyberN * eta1 / 4];
symmetric.prf(buf, seed, nonce);
CBD.mlkemCBD(this, buf, eta1);
}
public void getEta2Noise(byte[] seed, byte nonce)
{
byte[] buf = new byte[MLKEMEngine.KyberN * eta2 / 4];
symmetric.prf(buf, seed, nonce);
CBD.mlkemCBD(this, buf, eta2);
}
public void polySubtract(Poly b)
{
int i;
for (i = 0; i < MLKEMEngine.KyberN; i++)
{
this.setCoeffIndex(i, (short)(b.getCoeffIndex(i) - this.getCoeffIndex(i)));
}
}
public String toString()
{
StringBuffer out = new StringBuffer();
out.append("[");
for (int i = 0; i < coeffs.length; i++)
{
out.append(coeffs[i]);
if (i != coeffs.length - 1)
{
out.append(", ");
}
}
out.append("]");
return out.toString();
}
}

View File

@ -0,0 +1,272 @@
package org.bouncycastle.pqc.crypto.mlkem;
import java.util.Arrays;
class PolyVec
{
Poly[] vec;
private MLKEMEngine engine;
private int kyberK;
private int polyVecBytes;
public PolyVec(MLKEMEngine engine)
{
this.engine = engine;
this.kyberK = engine.getKyberK();
this.polyVecBytes = engine.getKyberPolyVecBytes();
this.vec = new Poly[kyberK];
for (int i = 0; i < kyberK; i++)
{
vec[i] = new Poly(engine);
}
}
public PolyVec()
throws Exception
{
throw new Exception("Requires Parameter");
}
public Poly getVectorIndex(int i)
{
return vec[i];
}
public void polyVecNtt()
{
int i;
for (i = 0; i < kyberK; i++)
{
this.getVectorIndex(i).polyNtt();
}
}
public void polyVecInverseNttToMont()
{
for (int i = 0; i < kyberK; i++)
{
this.getVectorIndex(i).polyInverseNttToMont();
}
}
public byte[] compressPolyVec()
{
int i, j, k;
this.conditionalSubQ();
short[] t;
byte[] r = new byte[engine.getKyberPolyVecCompressedBytes()];
int count = 0;
if (engine.getKyberPolyVecCompressedBytes() == kyberK * 320)
{
t = new short[4];
for (i = 0; i < kyberK; i++)
{
for (j = 0; j < MLKEMEngine.KyberN / 4; j++)
{
for (k = 0; k < 4; k++)
{
/*t[k] = (short)
(
(
((this.getVectorIndex(i).getCoeffIndex(4 * j + k) << 10)
+ (KyberEngine.KyberQ / 2))
/ KyberEngine.KyberQ)
& 0x3ff);*/
// Fix for KyberSlash2: division by KyberQ above is not
// constant time.
long t_k = this.getVectorIndex(i).getCoeffIndex(4 * j + k);
t_k <<= 10;
t_k += 1665;
t_k *= 1290167;
t_k >>= 32;
t_k &= 0x3ff;
t[k] = (short)t_k;
}
r[count + 0] = (byte)(t[0] >> 0);
r[count + 1] = (byte)((t[0] >> 8) | (t[1] << 2));
r[count + 2] = (byte)((t[1] >> 6) | (t[2] << 4));
r[count + 3] = (byte)((t[2] >> 4) | (t[3] << 6));
r[count + 4] = (byte)((t[3] >> 2));
count += 5;
}
}
}
else if (engine.getKyberPolyVecCompressedBytes() == kyberK * 352)
{
t = new short[8];
for (i = 0; i < kyberK; i++)
{
for (j = 0; j < MLKEMEngine.KyberN / 8; j++)
{
for (k = 0; k < 8; k++)
{
/*t[k] = (short)
(
(
((this.getVectorIndex(i).getCoeffIndex(8 * j + k) << 11)
+ (KyberEngine.KyberQ / 2))
/ KyberEngine.KyberQ)
& 0x7ff);*/
// Fix for KyberSlash2: division by KyberQ above is not
// constant time.
long t_k = this.getVectorIndex(i).getCoeffIndex(8 * j + k);
t_k <<= 11;
t_k += 1664;
t_k *= 645084;
t_k >>= 31;
t_k &= 0x7ff;
t[k] = (short)t_k;
}
r[count + 0] = (byte)((t[0] >> 0));
r[count + 1] = (byte)((t[0] >> 8) | (t[1] << 3));
r[count + 2] = (byte)((t[1] >> 5) | (t[2] << 6));
r[count + 3] = (byte)((t[2] >> 2));
r[count + 4] = (byte)((t[2] >> 10) | (t[3] << 1));
r[count + 5] = (byte)((t[3] >> 7) | (t[4] << 4));
r[count + 6] = (byte)((t[4] >> 4) | (t[5] << 7));
r[count + 7] = (byte)((t[5] >> 1));
r[count + 8] = (byte)((t[5] >> 9) | (t[6] << 2));
r[count + 9] = (byte)((t[6] >> 6) | (t[7] << 5));
r[count + 10] = (byte)((t[7] >> 3));
count += 11;
}
}
}
else
{
throw new RuntimeException("Kyber PolyVecCompressedBytes neither 320 * KyberK or 352 * KyberK!");
}
return r;
}
public void decompressPolyVec(byte[] compressedPolyVecCipherText)
{
int i, j, k, count = 0;
if (engine.getKyberPolyVecCompressedBytes() == (kyberK * 320))
{
short[] t = new short[4];
for (i = 0; i < kyberK; i++)
{
for (j = 0; j < MLKEMEngine.KyberN / 4; j++)
{
t[0] = (short)(((compressedPolyVecCipherText[count] & 0xFF) >> 0) | (short)((compressedPolyVecCipherText[count + 1] & 0xFF) << 8));
t[1] = (short)(((compressedPolyVecCipherText[count + 1] & 0xFF) >> 2) | (short)((compressedPolyVecCipherText[count + 2] & 0xFF) << 6));
t[2] = (short)(((compressedPolyVecCipherText[count + 2] & 0xFF) >> 4) | (short)((compressedPolyVecCipherText[count + 3] & 0xFF) << 4));
t[3] = (short)(((compressedPolyVecCipherText[count + 3] & 0xFF) >> 6) | (short)((compressedPolyVecCipherText[count + 4] & 0xFF) << 2));
count += 5;
for (k = 0; k < 4; k++)
{
this.vec[i].setCoeffIndex(4 * j + k, (short)(((t[k] & 0x3FF) * MLKEMEngine.KyberQ + 512) >> 10));
}
}
}
}
else if (engine.getKyberPolyVecCompressedBytes() == (kyberK * 352))
{
short[] t = new short[8];
for (i = 0; i < kyberK; i++)
{
for (j = 0; j < MLKEMEngine.KyberN / 8; j++)
{
t[0] = (short)(((compressedPolyVecCipherText[count] & 0xFF) >> 0) | ((short)(compressedPolyVecCipherText[count + 1] & 0xFF) << 8));
t[1] = (short)(((compressedPolyVecCipherText[count + 1] & 0xFF) >> 3) | ((short)(compressedPolyVecCipherText[count + 2] & 0xFF) << 5));
t[2] = (short)(((compressedPolyVecCipherText[count + 2] & 0xFF) >> 6) | ((short)(compressedPolyVecCipherText[count + 3] & 0xFF) << 2) | ((short)((compressedPolyVecCipherText[count + 4] & 0xFF) << 10)));
t[3] = (short)(((compressedPolyVecCipherText[count + 4] & 0xFF) >> 1) | ((short)(compressedPolyVecCipherText[count + 5] & 0xFF) << 7));
t[4] = (short)(((compressedPolyVecCipherText[count + 5] & 0xFF) >> 4) | ((short)(compressedPolyVecCipherText[count + 6] & 0xFF) << 4));
t[5] = (short)(((compressedPolyVecCipherText[count + 6] & 0xFF) >> 7) | ((short)(compressedPolyVecCipherText[count + 7] & 0xFF) << 1) | ((short)((compressedPolyVecCipherText[count + 8] & 0xFF) << 9)));
t[6] = (short)(((compressedPolyVecCipherText[count + 8] & 0xFF) >> 2) | ((short)(compressedPolyVecCipherText[count + 9] & 0xFF) << 6));
t[7] = (short)(((compressedPolyVecCipherText[count + 9] & 0xFF) >> 5) | ((short)(compressedPolyVecCipherText[count + 10] & 0xFF) << 3));
count += 11;
for (k = 0; k < 8; k++)
{
this.vec[i].setCoeffIndex(8 * j + k, (short)(((t[k] & 0x7FF) * MLKEMEngine.KyberQ + 1024) >> 11));
}
}
}
}
else
{
throw new RuntimeException("Kyber PolyVecCompressedBytes neither 320 * KyberK or 352 * KyberK!");
}
}
public static void pointwiseAccountMontgomery(Poly out, PolyVec inp1, PolyVec inp2, MLKEMEngine engine)
{
int i;
Poly t = new Poly(engine);
Poly.baseMultMontgomery(out, inp1.getVectorIndex(0), inp2.getVectorIndex(0));
for (i = 1; i < engine.getKyberK(); i++)
{
Poly.baseMultMontgomery(t, inp1.getVectorIndex(i), inp2.getVectorIndex(i));
out.addCoeffs(t);
}
out.reduce();
}
public void reducePoly()
{
int i;
for (i = 0; i < kyberK; i++)
{
this.getVectorIndex(i).reduce();
}
}
public void addPoly(PolyVec b)
{
int i;
for (i = 0; i < kyberK; i++)
{
this.getVectorIndex(i).addCoeffs(b.getVectorIndex(i));
}
}
public byte[] toBytes()
{
byte[] r = new byte[polyVecBytes];
for (int i = 0; i < kyberK; i++)
{
System.arraycopy(this.vec[i].toBytes(), 0, r, i * MLKEMEngine.KyberPolyBytes, MLKEMEngine.KyberPolyBytes);
}
return r;
}
public void fromBytes(byte[] inputBytes)
{
for (int i = 0; i < kyberK; i++)
{
this.getVectorIndex(i).fromBytes(Arrays.copyOfRange(inputBytes, i * MLKEMEngine.KyberPolyBytes, (i + 1) * MLKEMEngine.KyberPolyBytes));
}
}
public void conditionalSubQ()
{
for (int i = 0; i < kyberK; i++)
{
this.getVectorIndex(i).conditionalSubQ();
}
}
public String toString()
{
StringBuffer out = new StringBuffer();
out.append("[");
for (int i = 0; i < kyberK; i++)
{
out.append(vec[i].toString());
if (i != kyberK - 1)
{
out.append(", ");
}
}
out.append("]");
return out.toString();
}
}

View File

@ -0,0 +1,35 @@
package org.bouncycastle.pqc.crypto.mlkem;
class Reduce
{
public static short montgomeryReduce(int a)
{
int t;
short u;
u = (short)(a * MLKEMEngine.KyberQinv);
t = (int)(u * MLKEMEngine.KyberQ);
t = a - t;
t >>= 16;
return (short)t;
}
public static short barretReduce(short a)
{
short t;
long shift = (((long)1) << 26);
short v = (short)((shift + (MLKEMEngine.KyberQ / 2)) / MLKEMEngine.KyberQ);
t = (short)((v * a) >> 26);
t = (short)(t * MLKEMEngine.KyberQ);
return (short)(a - t);
}
public static short conditionalSubQ(short a)
{
a -= MLKEMEngine.KyberQ;
a += (a >> 15) & MLKEMEngine.KyberQ;
return a;
}
}

View File

@ -0,0 +1,94 @@
package org.bouncycastle.pqc.crypto.mlkem;
import org.bouncycastle.crypto.digests.SHA3Digest;
import org.bouncycastle.crypto.digests.SHAKEDigest;
abstract class Symmetric
{
final int xofBlockBytes;
abstract void hash_h(byte[] out, byte[] in, int outOffset);
abstract void hash_g(byte[] out, byte[] in);
abstract void xofAbsorb(byte[] seed, byte x, byte y);
abstract void xofSqueezeBlocks(byte[] out, int outOffset, int outLen);
abstract void prf(byte[] out, byte[] key, byte nonce);
abstract void kdf(byte[] out, byte[] in);
Symmetric(int blockBytes)
{
this.xofBlockBytes = blockBytes;
}
static class ShakeSymmetric
extends Symmetric
{
private final SHAKEDigest xof;
private final SHA3Digest sha3Digest512;
private final SHA3Digest sha3Digest256;
private final SHAKEDigest shakeDigest;
ShakeSymmetric()
{
super(168);
this.xof = new SHAKEDigest(128);
this.shakeDigest = new SHAKEDigest(256);
this.sha3Digest256 = new SHA3Digest(256);
this.sha3Digest512 = new SHA3Digest(512);
}
@Override
void hash_h(byte[] out, byte[] in, int outOffset)
{
sha3Digest256.update(in, 0, in.length);
sha3Digest256.doFinal(out, outOffset);
}
@Override
void hash_g(byte[] out, byte[] in)
{
sha3Digest512.update(in, 0, in.length);
sha3Digest512.doFinal(out, 0);
}
@Override
void xofAbsorb(byte[] seed, byte a, byte b)
{
xof.reset();
byte[] buf = new byte[seed.length + 2];
System.arraycopy(seed, 0, buf, 0, seed.length);
buf[seed.length] = a;
buf[seed.length + 1] = b;
xof.update(buf, 0, seed.length + 2);
}
@Override
void xofSqueezeBlocks(byte[] out, int outOffset, int outLen)
{
xof.doOutput(out, outOffset, outLen);
}
@Override
void prf(byte[] out, byte[] seed, byte nonce)
{
byte[] extSeed = new byte[seed.length + 1];
System.arraycopy(seed, 0, extSeed, 0, seed.length);
extSeed[seed.length] = nonce;
shakeDigest.update(extSeed, 0, extSeed.length);
shakeDigest.doFinal(out, 0, out.length);
}
@Override
void kdf(byte[] out, byte[] in)
{
shakeDigest.update(in, 0, in.length);
shakeDigest.doFinal(out, 0, out.length);
}
}
}

View File

@ -0,0 +1,6 @@
/**
* This is a small portion of bouncycastle 1.80 for MLKEM, modified to reduce dependencies.
* MLDSA and common classes are in i2p.jar (core).
* @since 0.9.67
*/
package org.bouncycastle.pqc.crypto.mlkem;

View File

@ -0,0 +1,65 @@
package org.bouncycastle.pqc.crypto.util;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.security.auth.DestroyFailedException;
import org.bouncycastle.crypto.SecretWithEncapsulation;
import org.bouncycastle.util.Util;
public class SecretWithEncapsulationImpl
implements SecretWithEncapsulation
{
private final AtomicBoolean hasBeenDestroyed = new AtomicBoolean(false);
private final byte[] sessionKey;
private final byte[] cipher_text;
public SecretWithEncapsulationImpl(byte[] sessionKey, byte[] cipher_text)
{
this.sessionKey = sessionKey;
this.cipher_text = cipher_text;
}
public byte[] getSecret()
{
byte[] clone = Util.clone(sessionKey);
checkDestroyed();
return clone;
}
public byte[] getEncapsulation()
{
byte[] clone = Util.clone(cipher_text);
checkDestroyed();
return clone;
}
public void destroy()
throws DestroyFailedException
{
if (!hasBeenDestroyed.getAndSet(true))
{
Arrays.fill(sessionKey, (byte) 0);
Arrays.fill(cipher_text, (byte) 0);
}
}
public boolean isDestroyed()
{
return hasBeenDestroyed.get();
}
void checkDestroyed()
{
if (isDestroyed())
{
throw new IllegalStateException("data has been destroyed");
}
}
}

View File

@ -0,0 +1,26 @@
package org.bouncycastle.util;
/**
* Interface for Memoable objects. Memoable objects allow the taking of a snapshot of their internal state
* via the copy() method and then resetting the object back to that state later using the reset() method.
*/
public interface Memoable
{
/**
* Produce a copy of this object with its configuration and in its current state.
* <p>
* The returned object may be used simply to store the state, or may be used as a similar object
* starting from the copied state.
*/
Memoable copy();
/**
* Restore a copied object state into this object.
* <p>
* Implementations of this method <em>should</em> try to avoid or minimise memory allocation to perform the reset.
*
* @param other an object originally {@link #copy() copied} from an object of the same type as this instance.
* @throws ClassCastException if the provided object is not of the correct type.
*/
void reset(Memoable other);
}

View File

@ -0,0 +1,419 @@
package org.bouncycastle.util;
/**
* Utility methods for converting byte arrays into ints and longs, and back again.
*/
public abstract class Pack
{
public static short bigEndianToShort(byte[] bs, int off)
{
int n = (bs[off] & 0xff) << 8;
n |= (bs[++off] & 0xff);
return (short)n;
}
public static int bigEndianToInt(byte[] bs, int off)
{
int n = bs[off] << 24;
n |= (bs[++off] & 0xff) << 16;
n |= (bs[++off] & 0xff) << 8;
n |= (bs[++off] & 0xff);
return n;
}
public static void bigEndianToInt(byte[] bs, int off, int[] ns)
{
for (int i = 0; i < ns.length; ++i)
{
ns[i] = bigEndianToInt(bs, off);
off += 4;
}
}
public static void bigEndianToInt(byte[] bs, int off, int[] ns, int nsOff, int nsLen)
{
for (int i = 0; i < nsLen; ++i)
{
ns[nsOff + i] = bigEndianToInt(bs, off);
off += 4;
}
}
public static byte[] intToBigEndian(int n)
{
byte[] bs = new byte[4];
intToBigEndian(n, bs, 0);
return bs;
}
public static void intToBigEndian(int n, byte[] bs, int off)
{
bs[off] = (byte)(n >>> 24);
bs[++off] = (byte)(n >>> 16);
bs[++off] = (byte)(n >>> 8);
bs[++off] = (byte)(n);
}
public static byte[] intToBigEndian(int[] ns)
{
byte[] bs = new byte[4 * ns.length];
intToBigEndian(ns, bs, 0);
return bs;
}
public static void intToBigEndian(int[] ns, byte[] bs, int off)
{
for (int i = 0; i < ns.length; ++i)
{
intToBigEndian(ns[i], bs, off);
off += 4;
}
}
public static void intToBigEndian(int[] ns, int nsOff, int nsLen, byte[] bs, int bsOff)
{
for (int i = 0; i < nsLen; ++i)
{
intToBigEndian(ns[nsOff + i], bs, bsOff);
bsOff += 4;
}
}
public static long bigEndianToLong(byte[] bs, int off)
{
int hi = bigEndianToInt(bs, off);
int lo = bigEndianToInt(bs, off + 4);
return ((long)(hi & 0xffffffffL) << 32) | (long)(lo & 0xffffffffL);
}
public static void bigEndianToLong(byte[] bs, int off, long[] ns)
{
for (int i = 0; i < ns.length; ++i)
{
ns[i] = bigEndianToLong(bs, off);
off += 8;
}
}
public static void bigEndianToLong(byte[] bs, int bsOff, long[] ns, int nsOff, int nsLen)
{
for (int i = 0; i < nsLen; ++i)
{
ns[nsOff + i] = bigEndianToLong(bs, bsOff);
bsOff += 8;
}
}
public static long bigEndianToLong(byte[] bs, int off, int len)
{
long x = 0;
for (int i = 0; i < len; ++i)
{
x |= (bs[i + off] & 0xFFL) << ((7 - i) << 3);
}
return x;
}
public static byte[] longToBigEndian(long n)
{
byte[] bs = new byte[8];
longToBigEndian(n, bs, 0);
return bs;
}
public static void longToBigEndian(long n, byte[] bs, int off)
{
intToBigEndian((int)(n >>> 32), bs, off);
intToBigEndian((int)(n & 0xffffffffL), bs, off + 4);
}
public static byte[] longToBigEndian(long[] ns)
{
byte[] bs = new byte[8 * ns.length];
longToBigEndian(ns, bs, 0);
return bs;
}
public static void longToBigEndian(long[] ns, byte[] bs, int off)
{
for (int i = 0; i < ns.length; ++i)
{
longToBigEndian(ns[i], bs, off);
off += 8;
}
}
public static void longToBigEndian(long[] ns, int nsOff, int nsLen, byte[] bs, int bsOff)
{
for (int i = 0; i < nsLen; ++i)
{
longToBigEndian(ns[nsOff + i], bs, bsOff);
bsOff += 8;
}
}
/**
* @param value The number
* @param bs The target.
* @param off Position in target to start.
* @param bytes number of bytes to write.
* @deprecated Will be removed
*/
public static void longToBigEndian(long value, byte[] bs, int off, int bytes)
{
for (int i = bytes - 1; i >= 0; i--)
{
bs[i + off] = (byte)(value & 0xff);
value >>>= 8;
}
}
public static short littleEndianToShort(byte[] bs, int off)
{
int n = bs[off] & 0xff;
n |= (bs[++off] & 0xff) << 8;
return (short)n;
}
public static int littleEndianToInt(byte[] bs, int off)
{
int n = bs[off] & 0xff;
n |= (bs[++off] & 0xff) << 8;
n |= (bs[++off] & 0xff) << 16;
n |= bs[++off] << 24;
return n;
}
public static int littleEndianToInt_High(byte[] bs, int off, int len)
{
return littleEndianToInt_Low(bs, off, len) << ((4 - len) << 3);
}
public static int littleEndianToInt_Low(byte[] bs, int off, int len)
{
// assert 1 <= len && len <= 4;
int result = bs[off] & 0xff;
int pos = 0;
for (int i = 1; i < len; ++i)
{
pos += 8;
result |= (bs[off + i] & 0xff) << pos;
}
return result;
}
public static void littleEndianToInt(byte[] bs, int off, int[] ns)
{
for (int i = 0; i < ns.length; ++i)
{
ns[i] = littleEndianToInt(bs, off);
off += 4;
}
}
public static void littleEndianToInt(byte[] bs, int bOff, int[] ns, int nOff, int count)
{
for (int i = 0; i < count; ++i)
{
ns[nOff + i] = littleEndianToInt(bs, bOff);
bOff += 4;
}
}
public static int[] littleEndianToInt(byte[] bs, int off, int count)
{
int[] ns = new int[count];
for (int i = 0; i < ns.length; ++i)
{
ns[i] = littleEndianToInt(bs, off);
off += 4;
}
return ns;
}
public static byte[] shortToLittleEndian(short n)
{
byte[] bs = new byte[2];
shortToLittleEndian(n, bs, 0);
return bs;
}
public static void shortToLittleEndian(short n, byte[] bs, int off)
{
bs[off] = (byte)(n);
bs[++off] = (byte)(n >>> 8);
}
public static byte[] shortToBigEndian(short n)
{
byte[] r = new byte[2];
shortToBigEndian(n, r, 0);
return r;
}
public static void shortToBigEndian(short n, byte[] bs, int off)
{
bs[off] = (byte)(n >>> 8);
bs[++off] = (byte)(n);
}
public static byte[] intToLittleEndian(int n)
{
byte[] bs = new byte[4];
intToLittleEndian(n, bs, 0);
return bs;
}
public static void intToLittleEndian(int n, byte[] bs, int off)
{
bs[off] = (byte)(n);
bs[++off] = (byte)(n >>> 8);
bs[++off] = (byte)(n >>> 16);
bs[++off] = (byte)(n >>> 24);
}
public static byte[] intToLittleEndian(int[] ns)
{
byte[] bs = new byte[4 * ns.length];
intToLittleEndian(ns, bs, 0);
return bs;
}
public static void intToLittleEndian(int[] ns, byte[] bs, int off)
{
for (int i = 0; i < ns.length; ++i)
{
intToLittleEndian(ns[i], bs, off);
off += 4;
}
}
public static void intToLittleEndian(int[] ns, int nsOff, int nsLen, byte[] bs, int bsOff)
{
for (int i = 0; i < nsLen; ++i)
{
intToLittleEndian(ns[nsOff + i], bs, bsOff);
bsOff += 4;
}
}
public static long littleEndianToLong(byte[] bs, int off)
{
int lo = littleEndianToInt(bs, off);
int hi = littleEndianToInt(bs, off + 4);
return ((long)(hi & 0xffffffffL) << 32) | (long)(lo & 0xffffffffL);
}
public static void littleEndianToLong(byte[] bs, int off, long[] ns)
{
for (int i = 0; i < ns.length; ++i)
{
ns[i] = littleEndianToLong(bs, off);
off += 8;
}
}
public static long littleEndianToLong(byte[] input, int off, int len)
{
long result = 0;
for (int i = 0; i < len; ++i)
{
result |= (input[off + i] & 0xFFL) << (i << 3);
}
return result;
}
public static void littleEndianToLong(byte[] bs, int bsOff, long[] ns, int nsOff, int nsLen)
{
for (int i = 0; i < nsLen; ++i)
{
ns[nsOff + i] = littleEndianToLong(bs, bsOff);
bsOff += 8;
}
}
public static void longToLittleEndian_High(long n, byte[] bs, int off, int len)
{
//Debug.Assert(1 <= len && len <= 8);
int pos = 56;
bs[off] = (byte)(n >>> pos);
for (int i = 1; i < len; ++i)
{
pos -= 8;
bs[off + i] = (byte)(n >>> pos);
}
}
public static void longToLittleEndian(long n, byte[] bs, int off, int len)
{
for (int i = 0; i < len; ++i)
{
bs[off + i] = (byte)(n >>> (i << 3));
}
}
// public static void longToLittleEndian_Low(long n, byte[] bs, int off, int len)
// {
// longToLittleEndian_High(n << ((8 - len) << 3), bs, off, len);
// }
public static long littleEndianToLong_High(byte[] bs, int off, int len)
{
return littleEndianToLong_Low(bs, off, len) << ((8 - len) << 3);
}
public static long littleEndianToLong_Low(byte[] bs, int off, int len)
{
//Debug.Assert(1 <= len && len <= 8);
long result = bs[off] & 0xFF;
for (int i = 1; i < len; ++i)
{
result <<= 8;
result |= bs[off + i] & 0xFF;
}
return result;
}
public static byte[] longToLittleEndian(long n)
{
byte[] bs = new byte[8];
longToLittleEndian(n, bs, 0);
return bs;
}
public static void longToLittleEndian(long n, byte[] bs, int off)
{
intToLittleEndian((int)(n & 0xffffffffL), bs, off);
intToLittleEndian((int)(n >>> 32), bs, off + 4);
}
public static byte[] longToLittleEndian(long[] ns)
{
byte[] bs = new byte[8 * ns.length];
longToLittleEndian(ns, bs, 0);
return bs;
}
public static void longToLittleEndian(long[] ns, byte[] bs, int off)
{
for (int i = 0; i < ns.length; ++i)
{
longToLittleEndian(ns[i], bs, off);
off += 8;
}
}
public static void longToLittleEndian(long[] ns, int nsOff, int nsLen, byte[] bs, int bsOff)
{
for (int i = 0; i < nsLen; ++i)
{
longToLittleEndian(ns[nsOff + i], bs, bsOff);
bsOff += 8;
}
}
}

View File

@ -0,0 +1,52 @@
package org.bouncycastle.util;
import java.util.Arrays;
import net.i2p.data.DataHelper;
public class Util
{
public static byte[] clone(byte[] a)
{
return Arrays.copyOf(a, a.length);
}
public static byte[] concatenate(byte[][] arrays)
{
int size = 0;
for (int i = 0; i != arrays.length; i++)
{
size += arrays[i].length;
}
byte[] rv = new byte[size];
int offSet = 0;
for (int i = 0; i != arrays.length; i++)
{
System.arraycopy(arrays[i], 0, rv, offSet, arrays[i].length);
offSet += arrays[i].length;
}
return rv;
}
public static byte[] concatenate(byte[] a, byte[] b)
{
byte[] rv = Arrays.copyOf(a, a.length + b.length);
System.arraycopy(b, 0, rv, a.length, b.length);
return rv;
}
public static byte[] append(byte[] a, byte b)
{
byte[] rv = Arrays.copyOf(a, a.length + 1);
rv[a.length] = b;
return rv;
}
public static boolean constantTimeAreEqual(byte[] a, byte[] b)
{
return a.length == b.length && DataHelper.eqCT(a, 0, b, 0, a.length);
}
}

View File

@ -0,0 +1,4 @@
/**
* General purpose utility classes used throughout the APIs.
*/
package org.bouncycastle.util;