forked from I2P_Developers/i2p.i2p
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:
@ -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)
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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.*" />
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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++) {
|
||||
|
21
licenses/LICENSE-Bouncycastle.txt
Normal file
21
licenses/LICENSE-Bouncycastle.txt
Normal 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.
|
||||
*/
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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())
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
231
router/java/src/net/i2p/router/crypto/ratchet/MuxedPQSKM.java
Normal file
231
router/java/src/net/i2p/router/crypto/ratchet/MuxedPQSKM.java
Normal 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) {
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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, > 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);
|
||||
|
@ -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;
|
||||
|
@ -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))
|
||||
|
@ -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,
|
||||
|
@ -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 &&
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
package org.bouncycastle.crypto;
|
||||
|
||||
/**
|
||||
* all parameter classes implement this.
|
||||
*/
|
||||
public interface CipherParameters
|
||||
{
|
||||
}
|
48
router/java/src/org/bouncycastle/crypto/CryptoException.java
Normal file
48
router/java/src/org/bouncycastle/crypto/CryptoException.java
Normal 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package org.bouncycastle.crypto;
|
||||
|
||||
public interface CryptoServiceProperties
|
||||
{
|
||||
int bitsOfSecurity();
|
||||
|
||||
String getServiceName();
|
||||
|
||||
CryptoServicePurpose getPurpose();
|
||||
|
||||
Object getParams();
|
||||
}
|
@ -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
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
51
router/java/src/org/bouncycastle/crypto/Digest.java
Normal file
51
router/java/src/org/bouncycastle/crypto/Digest.java
Normal 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();
|
||||
}
|
13
router/java/src/org/bouncycastle/crypto/ExtendedDigest.java
Normal file
13
router/java/src/org/bouncycastle/crypto/ExtendedDigest.java
Normal 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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
30
router/java/src/org/bouncycastle/crypto/Xof.java
Normal file
30
router/java/src/org/bouncycastle/crypto/Xof.java
Normal 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);
|
||||
}
|
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
426
router/java/src/org/bouncycastle/crypto/digests/LongDigest.java
Normal file
426
router/java/src/org/bouncycastle/crypto/digests/LongDigest.java
Normal 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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
150
router/java/src/org/bouncycastle/crypto/digests/SHAKEDigest.java
Normal file
150
router/java/src/org/bouncycastle/crypto/digests/SHAKEDigest.java
Normal 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Message digest classes.
|
||||
*/
|
||||
package org.bouncycastle.crypto.digests;
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Base classes for the lightweight API.
|
||||
*/
|
||||
package org.bouncycastle.crypto;
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Classes for parameter objects for ciphers and generators.
|
||||
*/
|
||||
package org.bouncycastle.crypto.params;
|
6
router/java/src/org/bouncycastle/package-info.java
Normal file
6
router/java/src/org/bouncycastle/package-info.java
Normal 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;
|
@ -0,0 +1,8 @@
|
||||
package org.bouncycastle.pqc.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
|
||||
public interface KEMParameters
|
||||
extends CipherParameters
|
||||
{
|
||||
}
|
84
router/java/src/org/bouncycastle/pqc/crypto/mlkem/CBD.java
Normal file
84
router/java/src/org/bouncycastle/pqc/crypto/mlkem/CBD.java
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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]);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
100
router/java/src/org/bouncycastle/pqc/crypto/mlkem/Ntt.java
Normal file
100
router/java/src/org/bouncycastle/pqc/crypto/mlkem/Ntt.java
Normal 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);
|
||||
}
|
||||
}
|
354
router/java/src/org/bouncycastle/pqc/crypto/mlkem/Poly.java
Normal file
354
router/java/src/org/bouncycastle/pqc/crypto/mlkem/Poly.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
272
router/java/src/org/bouncycastle/pqc/crypto/mlkem/PolyVec.java
Normal file
272
router/java/src/org/bouncycastle/pqc/crypto/mlkem/PolyVec.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
26
router/java/src/org/bouncycastle/util/Memoable.java
Normal file
26
router/java/src/org/bouncycastle/util/Memoable.java
Normal 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);
|
||||
}
|
419
router/java/src/org/bouncycastle/util/Pack.java
Normal file
419
router/java/src/org/bouncycastle/util/Pack.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
52
router/java/src/org/bouncycastle/util/Util.java
Normal file
52
router/java/src/org/bouncycastle/util/Util.java
Normal 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);
|
||||
}
|
||||
}
|
4
router/java/src/org/bouncycastle/util/package-info.java
Normal file
4
router/java/src/org/bouncycastle/util/package-info.java
Normal file
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* General purpose utility classes used throughout the APIs.
|
||||
*/
|
||||
package org.bouncycastle.util;
|
Reference in New Issue
Block a user