Skip to content

Commit 68c3816

Browse files
gefeilidghgit
authored andcommitted
Add javadoc and add random tests.
1 parent 2613693 commit 68c3816

File tree

6 files changed

+398
-179
lines changed

6 files changed

+398
-179
lines changed

core/src/main/java/org/bouncycastle/crypto/kems/SAKKEKEMExtractor.java

Lines changed: 95 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.math.BigInteger;
44

5+
import org.bouncycastle.crypto.Digest;
56
import org.bouncycastle.crypto.EncapsulatedSecretExtractor;
67
import org.bouncycastle.crypto.params.SAKKEPrivateKeyParameters;
78
import org.bouncycastle.crypto.params.SAKKEPublicKeyParameters;
@@ -10,7 +11,22 @@
1011
import org.bouncycastle.util.Arrays;
1112
import org.bouncycastle.util.BigIntegers;
1213

13-
14+
/**
15+
* Implements the receiver side of the SAKKE (Sakai-Kasahara Key Encryption) protocol
16+
* as defined in RFC 6508. This class extracts the shared secret value (SSV) from
17+
* encapsulated data using the receiver's private key.
18+
* <p>
19+
* The extraction process follows these steps (RFC 6508, Section 6.2.2):
20+
* <ol>
21+
* <li>Parse encapsulated data into R_(b,S) and H</li>
22+
* <li>Compute pairing result w = &lt;R_(b,S), K_(b,S)&gt;</li>
23+
* <li>Recover SSV via SSV = H XOR HashToIntegerRange(w, 2^n)</li>
24+
* <li>Validate R_(b,S) by recomputing it with derived parameters</li>
25+
* </ol>
26+
* </p>
27+
*
28+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc6508">Sakai-Kasahara Key Encryption (SAKKE)</a>
29+
*/
1430
public class SAKKEKEMExtractor
1531
implements EncapsulatedSecretExtractor
1632
{
@@ -22,65 +38,88 @@ public class SAKKEKEMExtractor
2238
private final ECPoint K_bs;
2339
private final int n; // Security parameter
2440
private final BigInteger identifier;
25-
41+
private final Digest digest;
42+
43+
/**
44+
* Initializes the extractor with cryptographic parameters from the receiver's private key.
45+
*
46+
* @param privateKey The receiver's private key containing public parameters
47+
* (curve, prime, generator, etc.) and the Receiver Secret Key (RSK).
48+
* Must not be {@code null}.
49+
*/
2650
public SAKKEKEMExtractor(SAKKEPrivateKeyParameters privateKey)
2751
{
2852
SAKKEPublicKeyParameters publicKey = privateKey.getPublicParams();
2953
this.curve = publicKey.getCurve();
3054
this.q = publicKey.getQ();
31-
this.P = publicKey.getP();
55+
this.P = publicKey.getPoint();
3256
this.p = publicKey.getPrime();
3357
this.Z_S = publicKey.getZ();
34-
this.K_bs = privateKey.getRSK();
35-
this.n = publicKey.getN();
3658
this.identifier = publicKey.getIdentifier();
59+
this.K_bs = P.multiply(this.identifier.add(privateKey.getMasterSecret()).modInverse(q)).normalize();
60+
this.n = publicKey.getN();
61+
62+
this.digest = publicKey.getDigest();
3763
}
3864

65+
/**
66+
* Extracts the shared secret value (SSV) from encapsulated data as per RFC 6508.
67+
*
68+
* @param encapsulation The encapsulated data containing:
69+
* <ul>
70+
* <li>R_(b,S): Elliptic curve point (uncompressed format, 257 bytes)</li>
71+
* <li>H: Integer value (n/8 bytes)</li>
72+
* </ul>
73+
* @return The extracted SSV as a byte array.
74+
* @throws IllegalStateException If: Validation of R_(b,S) fails
75+
*/
3976
@Override
4077
public byte[] extractSecret(byte[] encapsulation)
4178
{
42-
try
43-
{
44-
// Step 1: Parse Encapsulated Data (R_bS, H)
45-
ECPoint R_bS = curve.decodePoint(Arrays.copyOfRange(encapsulation, 0, 257));
46-
BigInteger H = new BigInteger(Arrays.copyOfRange(encapsulation, 257, 274));
47-
48-
// Step 2: Compute w = <R_bS, K_bS> using pairing
49-
BigInteger w = computePairing(R_bS, K_bs, p, q);
50-
51-
// Step 3: Compute SSV = H XOR HashToIntegerRange(w, 2^n)
52-
BigInteger twoToN = BigInteger.ONE.shiftLeft(n);
53-
BigInteger mask = SAKKEUtils.hashToIntegerRange(w.toByteArray(), twoToN);
54-
BigInteger ssv = H.xor(mask);
55-
56-
// Step 4: Compute r = HashToIntegerRange(SSV || b)
57-
BigInteger b = identifier;
58-
BigInteger r = SAKKEUtils.hashToIntegerRange(Arrays.concatenate(ssv.toByteArray(), b.toByteArray()), q);
59-
60-
// Step 5: Validate R_bS
61-
ECPoint bP = P.multiply(b).normalize();
62-
ECPoint Test = bP.add(Z_S).multiply(r).normalize();
63-
if (!R_bS.equals(Test))
64-
{
65-
throw new IllegalStateException("Validation of R_bS failed");
66-
}
67-
68-
return BigIntegers.asUnsignedByteArray(n / 8, ssv);
69-
}
70-
catch (Exception e)
79+
// Step 1: Parse Encapsulated Data (R_bS, H)
80+
ECPoint R_bS = curve.decodePoint(Arrays.copyOfRange(encapsulation, 0, 257));
81+
BigInteger H = BigIntegers.fromUnsignedByteArray(encapsulation, 257, 16);
82+
83+
// Step 2: Compute w = <R_bS, K_bS> using pairing
84+
BigInteger w = computePairing(R_bS, K_bs, p, q);
85+
86+
// Step 3: Compute SSV = H XOR HashToIntegerRange(w, 2^n)
87+
BigInteger twoToN = BigInteger.ONE.shiftLeft(n);
88+
BigInteger mask = SAKKEKEMSGenerator.hashToIntegerRange(w.toByteArray(), twoToN, digest);
89+
BigInteger ssv = H.xor(mask).mod(p);
90+
91+
// Step 4: Compute r = HashToIntegerRange(SSV || b)
92+
BigInteger b = identifier;
93+
BigInteger r = SAKKEKEMSGenerator.hashToIntegerRange(Arrays.concatenate(ssv.toByteArray(), b.toByteArray()), q, digest);
94+
95+
// Step 5: Validate R_bS
96+
ECPoint bP = P.multiply(b).normalize();
97+
ECPoint Test = bP.add(Z_S).multiply(r).normalize();
98+
if (!R_bS.equals(Test))
7199
{
72-
throw new IllegalStateException("SAKKE extraction failed: " + e.getMessage());
100+
throw new IllegalStateException("Validation of R_bS failed");
73101
}
102+
103+
return BigIntegers.asUnsignedByteArray(n / 8, ssv);
74104
}
75105

76106
@Override
77107
public int getEncapsulationLength()
78108
{
79-
return 0;
109+
return 273; //257 (length of ECPoint) + 16 (length of Hash)
80110
}
81111

82-
83-
public static BigInteger computePairing(ECPoint R, ECPoint Q, BigInteger p, BigInteger q)
112+
/**
113+
* Computes the Tate-Lichtenbaum pairing &lt;R, Q&gt; for SAKKE validation.
114+
* Follows the pairing algorithm described in RFC 6508, Section 3.2.
115+
*
116+
* @param R First pairing input (elliptic curve point)
117+
* @param Q Second pairing input (elliptic curve point)
118+
* @param p Prime field characteristic
119+
* @param q Subgroup order
120+
* @return Pairing result in PF_p[q], represented as a field element
121+
*/
122+
static BigInteger computePairing(ECPoint R, ECPoint Q, BigInteger p, BigInteger q)
84123
{
85124
// v = (1,0) in F_p^2
86125
BigInteger[] v = new BigInteger[]{BigInteger.ONE, BigInteger.ZERO};
@@ -133,6 +172,16 @@ public static BigInteger computePairing(ECPoint R, ECPoint Q, BigInteger p, BigI
133172
return v[1].multiply(v[0].modInverse(p)).mod(p);
134173
}
135174

175+
/**
176+
* Performs multiplication in F_p^2 field.
177+
*
178+
* @param x_real Real component of first operand
179+
* @param x_imag Imaginary component of first operand
180+
* @param y_real Real component of second operand
181+
* @param y_imag Imaginary component of second operand
182+
* @param p Prime field characteristic
183+
* @return Result of multiplication in F_p^2 as [real, imaginary] array
184+
*/
136185
static BigInteger[] fp2Multiply(BigInteger x_real, BigInteger x_imag, BigInteger y_real, BigInteger y_imag, BigInteger p)
137186
{
138187
return new BigInteger[]{
@@ -141,6 +190,14 @@ static BigInteger[] fp2Multiply(BigInteger x_real, BigInteger x_imag, BigInteger
141190
};
142191
}
143192

193+
/**
194+
* Computes squaring operation in F_p^2 field.
195+
*
196+
* @param currentX Real component of input
197+
* @param currentY Imaginary component of input
198+
* @param p Prime field characteristic
199+
* @return Squared result in F_p^2 as [newX, newY] array
200+
*/
144201
static BigInteger[] fp2PointSquare(BigInteger currentX, BigInteger currentY, BigInteger p)
145202
{
146203
BigInteger xPlusY = currentX.add(currentY).mod(p);
Lines changed: 106 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.bouncycastle.crypto.kems;
22

3+
import org.bouncycastle.crypto.Digest;
34
import org.bouncycastle.crypto.EncapsulatedSecretGenerator;
45
import org.bouncycastle.crypto.SecretWithEncapsulation;
56
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
@@ -12,19 +13,59 @@
1213
import java.math.BigInteger;
1314
import java.security.SecureRandom;
1415

16+
/**
17+
* This class implements the SAKKE (Sakai-Kasahara Key Encryption) Key Encapsulation Mechanism
18+
* as defined in RFC 6508. It generates an encapsulated shared secret value (SSV) using
19+
* Identity-Based Encryption (IBE) for secure transmission from a Sender to a Receiver.
20+
* <p>
21+
* The algorithm follows these steps (as per RFC 6508, Section 6.2.1):
22+
* <ol>
23+
* <li>Generate a random SSV in the range [0, 2^n - 1].</li>
24+
* <li>Compute r = HashToIntegerRange(SSV || b, q).</li>
25+
* <li>Compute R_(b,S) = [r]([b]P + Z_S) on the elliptic curve.</li>
26+
* <li>Compute H = SSV XOR HashToIntegerRange(g^r, 2^n).</li>
27+
* <li>Encode the encapsulated data (R_(b,S), H).</li>
28+
* </ol>
29+
* </p>
30+
*
31+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc6508">RFC 6508: Sakai-Kasahara Key Encryption (SAKKE)</a>
32+
*/
1533
public class SAKKEKEMSGenerator
1634
implements EncapsulatedSecretGenerator
1735
{
1836
private final SecureRandom random;
1937

38+
/**
39+
* Constructs a SAKKEKEMSGenerator with the specified source of randomness.
40+
*
41+
* @param random a {@link SecureRandom} instance for generating cryptographically secure random values.
42+
* Must not be {@code null}.
43+
*/
2044
public SAKKEKEMSGenerator(SecureRandom random)
2145
{
2246
this.random = random;
2347
}
2448

49+
/**
50+
* Generates an encapsulated shared secret value (SSV) using the recipient's public key parameters
51+
* as specified in RFC 6508, Section 6.2.1.
52+
* <p>
53+
* This method performs the following operations:
54+
* <ul>
55+
* <li>Derives cryptographic parameters from the recipient's public key.</li>
56+
* <li>Generates a random SSV and computes the encapsulation components (R_(b,S), H).</li>
57+
* <li>Encodes the encapsulated data as specified in RFC 6508, Section 4.</li>
58+
* </ul>
59+
* </p>
60+
*
61+
* @param recipientKey the recipient's public key parameters. Must be an instance of
62+
* {@link SAKKEPublicKeyParameters}. Must not be {@code null}.
63+
* @return a {@link SecretWithEncapsulation} containing the SSV and the encapsulated data.
64+
*/
2565
@Override
2666
public SecretWithEncapsulation generateEncapsulated(AsymmetricKeyParameter recipientKey)
2767
{
68+
// Extract public parameters from the recipient's key
2869
SAKKEPublicKeyParameters keyParameters = (SAKKEPublicKeyParameters)recipientKey;
2970
ECPoint Z = keyParameters.getZ();
3071
BigInteger b = keyParameters.getIdentifier();
@@ -33,54 +74,31 @@ public SecretWithEncapsulation generateEncapsulated(AsymmetricKeyParameter recip
3374
BigInteger g = keyParameters.getG();
3475
int n = keyParameters.getN();
3576
ECCurve curve = keyParameters.getCurve();
36-
ECPoint P = keyParameters.getP();
77+
ECPoint P = keyParameters.getPoint();
78+
Digest digest = keyParameters.getDigest();
3779

3880
// 1. Generate random SSV in range [0, 2^n - 1]
3981
BigInteger ssv = new BigInteger(n, random);
4082

41-
4283
// 2. Compute r = HashToIntegerRange(SSV || b, q)
43-
44-
BigInteger r = SAKKEUtils.hashToIntegerRange(Arrays.concatenate(ssv.toByteArray(), b.toByteArray()), q);
84+
BigInteger r = hashToIntegerRange(Arrays.concatenate(ssv.toByteArray(), b.toByteArray()), q, digest);
4585

4686

4787
// 3. Compute R_(b,S) = [r]([b]P + Z_S)
4888
ECPoint bP = P.multiply(b).normalize();
49-
ECPoint R_bS = bP.add(Z).multiply(r).normalize(); // [r]([b]P + Z_S)
89+
ECPoint R_bS = bP.add(Z).multiply(r).normalize();
5090

5191
// 4. Compute H = SSV XOR HashToIntegerRange( g^r, 2^n )
52-
BigInteger[] v = fp2Exponentiate(p, BigInteger.ONE, g, r, curve);
53-
BigInteger g_r = v[1].multiply(v[0].modInverse(p)).mod(p);
54-
55-
BigInteger mask = SAKKEUtils.hashToIntegerRange(g_r.toByteArray(), BigInteger.ONE.shiftLeft(n)); // 2^n
56-
57-
BigInteger H = ssv.xor(mask);
58-
//System.out.println(new String(Hex.encode(H.toByteArray())));
59-
// 5. Encode encapsulated data (R_bS, H)
60-
byte[] encapsulated = Arrays.concatenate(R_bS.getEncoded(false), H.toByteArray());
61-
62-
return new SecretWithEncapsulationImpl(
63-
BigIntegers.asUnsignedByteArray(n / 8, ssv), // Output SSV as key material
64-
encapsulated
65-
);
66-
}
67-
68-
69-
public static BigInteger[] fp2Exponentiate(
70-
BigInteger p,
71-
BigInteger pointX,
72-
BigInteger pointY,
73-
BigInteger n,
74-
ECCurve curve)
75-
{
76-
BigInteger[] result = new BigInteger[2];
92+
BigInteger pointX = BigInteger.ONE;
93+
BigInteger pointY = g;
94+
BigInteger[] v = new BigInteger[2];
7795

7896
// Initialize result with the original point
79-
BigInteger currentX = pointX;
80-
BigInteger currentY = pointY;
97+
BigInteger currentX = BigInteger.ONE;
98+
BigInteger currentY = g;
8199
ECPoint current = curve.createPoint(currentX, currentY);
82100

83-
int numBits = n.bitLength();
101+
int numBits = r.bitLength();
84102
BigInteger[] rlt;
85103
// Process bits from MSB-1 down to 0
86104
for (int i = numBits - 2; i >= 0; i--)
@@ -91,7 +109,7 @@ public static BigInteger[] fp2Exponentiate(
91109
currentX = rlt[0];
92110
currentY = rlt[1];
93111
// Multiply if bit is set
94-
if (n.testBit(i))
112+
if (r.testBit(i))
95113
{
96114
rlt = SAKKEKEMExtractor.fp2Multiply(currentX, currentY, pointX, pointY, p);
97115

@@ -100,8 +118,59 @@ public static BigInteger[] fp2Exponentiate(
100118
}
101119
}
102120

103-
result[0] = currentX;
104-
result[1] = currentY;
105-
return result;
121+
v[0] = currentX;
122+
v[1] = currentY;
123+
BigInteger g_r = v[1].multiply(v[0].modInverse(p)).mod(p);
124+
125+
BigInteger mask = hashToIntegerRange(g_r.toByteArray(), BigInteger.ONE.shiftLeft(n), digest); // 2^n
126+
127+
BigInteger H = ssv.xor(mask);
128+
// 5. Encode encapsulated data (R_bS, H)
129+
// byte[] encapsulated = Arrays.concatenate(new byte[]{(byte)0x04},
130+
// BigIntegers.asUnsignedByteArray(n, R_bS.getXCoord().toBigInteger()),
131+
// BigIntegers.asUnsignedByteArray(n, R_bS.getYCoord().toBigInteger()),
132+
// BigIntegers.asUnsignedByteArray(16, H));
133+
byte[] encapsulated = Arrays.concatenate(R_bS.getEncoded(false), BigIntegers.asUnsignedByteArray(16, H));
134+
135+
return new SecretWithEncapsulationImpl(
136+
BigIntegers.asUnsignedByteArray(n / 8, ssv), // Output SSV as key material
137+
encapsulated
138+
);
139+
}
140+
141+
static BigInteger hashToIntegerRange(byte[] input, BigInteger q, Digest digest)
142+
{
143+
// RFC 6508 Section 5.1: Hashing to an Integer Range
144+
byte[] hash = new byte[digest.getDigestSize()];
145+
146+
// Step 1: Compute A = hashfn(s)
147+
digest.update(input, 0, input.length);
148+
digest.doFinal(hash, 0);
149+
byte[] A = hash.clone();
150+
151+
// Step 2: Initialize h_0 to all-zero bytes of hashlen size
152+
byte[] h = new byte[digest.getDigestSize()];
153+
154+
// Step 3: Compute l = Ceiling(lg(n)/hashlen)
155+
int l = q.bitLength() >> 8;
156+
157+
BigInteger v = BigInteger.ZERO;
158+
159+
// Step 4: Compute h_i and v_i
160+
for (int i = 0; i <= l; i++)
161+
{
162+
// h_i = hashfn(h_{i-1})
163+
digest.update(h, 0, h.length);
164+
digest.doFinal(h, 0);
165+
// v_i = hashfn(h_i || A)
166+
digest.update(h, 0, h.length);
167+
digest.update(A, 0, A.length);
168+
byte[] v_i = new byte[digest.getDigestSize()];
169+
digest.doFinal(v_i, 0);
170+
// Append v_i to v'
171+
v = v.shiftLeft(v_i.length * 8).add(new BigInteger(1, v_i));
172+
}
173+
// Step 6: v = v' mod n
174+
return v.mod(q);
106175
}
107176
}

0 commit comments

Comments
 (0)