|
| 1 | +package org.bouncycastle.crypto.agreement; |
| 2 | + |
| 3 | +import java.io.ByteArrayOutputStream; |
| 4 | +import java.math.BigInteger; |
| 5 | +import java.security.SecureRandom; |
| 6 | + |
| 7 | +import org.bouncycastle.crypto.digests.SM9Sm3; |
| 8 | +import org.bouncycastle.crypto.params.SM9EncMasterPrivateKeyParameters; |
| 9 | +import org.bouncycastle.crypto.params.SM9EncPrivateKeyParameters; |
| 10 | +import org.bouncycastle.crypto.CryptoServicesRegistrar; |
| 11 | +import org.bouncycastle.crypto.digests.SM3Digest; |
| 12 | +import org.bouncycastle.math.ec.ECConstants; |
| 13 | +import org.bouncycastle.math.ec.ECPoint; |
| 14 | +import org.bouncycastle.math.ec.sm9.Fp12; |
| 15 | +import org.bouncycastle.math.ec.sm9.SM9Curve; |
| 16 | +import org.bouncycastle.math.ec.sm9.SM9G2Point; |
| 17 | +import org.bouncycastle.math.ec.sm9.SM9Pairing; |
| 18 | +import org.bouncycastle.util.BigIntegers; |
| 19 | + |
| 20 | +/** |
| 21 | + * The SM9 key exchange protocol (GM/T 0044.3-2016). |
| 22 | + * <p> |
| 23 | + * Usage per party: construct with your own key-exchange private key (derived with |
| 24 | + * hid = 0x02), the peer's identity, and whether you are the initiator (user A) or |
| 25 | + * responder (user B). Call {@link #generateEphemeral} to produce your R value, |
| 26 | + * exchange R values, then call {@link #calculateKey} with the peer's R to obtain |
| 27 | + * the shared key. The optional key-confirmation tags are then available via |
| 28 | + * {@link #getResponderConfirmation()} (S_B) and {@link #getInitiatorConfirmation()} |
| 29 | + * (S_A). |
| 30 | + */ |
| 31 | +public class SM9KeyExchange |
| 32 | +{ |
| 33 | + private final SM9EncPrivateKeyParameters key; |
| 34 | + private final byte[] peerIdentity; |
| 35 | + private final boolean initiator; |
| 36 | + |
| 37 | + private BigInteger ephemeralScalar; |
| 38 | + private ECPoint ephemeralPoint; |
| 39 | + |
| 40 | + // retained after calculateKey for the confirmation tags |
| 41 | + private Fp12 g1; |
| 42 | + private Fp12 g2; |
| 43 | + private Fp12 g3; |
| 44 | + private byte[] idA; |
| 45 | + private byte[] idB; |
| 46 | + private byte[] raBytes; |
| 47 | + private byte[] rbBytes; |
| 48 | + |
| 49 | + public SM9KeyExchange(SM9EncPrivateKeyParameters key, byte[] peerIdentity, boolean initiator) |
| 50 | + { |
| 51 | + this.key = key; |
| 52 | + this.peerIdentity = peerIdentity; |
| 53 | + this.initiator = initiator; |
| 54 | + } |
| 55 | + |
| 56 | + /** |
| 57 | + * Generate this party's ephemeral value R = [r]Q_peer (a G1 point) and retain |
| 58 | + * the ephemeral scalar r. Q_peer = [H1(peerId||0x02, N)]P1 + P_pub-e. |
| 59 | + */ |
| 60 | + public ECPoint generateEphemeral(SecureRandom random) |
| 61 | + { |
| 62 | + ECPoint qPeer = key.getMasterPublicKey().recipientPoint( |
| 63 | + peerIdentity, SM9EncMasterPrivateKeyParameters.HID_EXCHANGE); |
| 64 | + SecureRandom rand = CryptoServicesRegistrar.getSecureRandom(random); |
| 65 | + ephemeralScalar = BigIntegers.createRandomInRange( |
| 66 | + ECConstants.ONE, SM9Curve.N.subtract(ECConstants.ONE), rand); |
| 67 | + ephemeralPoint = SM9Curve.multiplySecure(qPeer, ephemeralScalar).normalize(); |
| 68 | + return ephemeralPoint; |
| 69 | + } |
| 70 | + |
| 71 | + /** |
| 72 | + * Compute the shared key of {@code klenBits} bits from the peer's ephemeral |
| 73 | + * value {@code peerR}. Must be called after {@link #generateEphemeral}. |
| 74 | + */ |
| 75 | + public byte[] calculateKey(int klenBits, ECPoint peerR) |
| 76 | + { |
| 77 | + if (klenBits <= 0) |
| 78 | + { |
| 79 | + // match SM9KEMGenerator: a non-positive length has no KDF output |
| 80 | + throw new IllegalArgumentException("klenBits must be positive"); |
| 81 | + } |
| 82 | + if (ephemeralPoint == null) |
| 83 | + { |
| 84 | + throw new IllegalStateException("generateEphemeral must be called first"); |
| 85 | + } |
| 86 | + peerR = peerR.normalize(); |
| 87 | + if (peerR.isInfinity() || !peerR.isValid()) |
| 88 | + { |
| 89 | + throw new IllegalArgumentException("invalid SM9 peer ephemeral point"); |
| 90 | + } |
| 91 | + |
| 92 | + BigInteger r = ephemeralScalar; |
| 93 | + Fp12 gPP = key.getMasterPublicKey().pairingWithP2(); // e(P_pub-e, P2) |
| 94 | + SM9G2Point de = key.getPrivatePoint(); |
| 95 | + |
| 96 | + if (initiator) |
| 97 | + { |
| 98 | + g1 = gPP.powSecure(r); // e(P_pub-e,P2)^rA |
| 99 | + g2 = SM9Pairing.pairing(peerR, de); // e(RB, deA) |
| 100 | + g3 = g2.powSecure(r); |
| 101 | + } |
| 102 | + else |
| 103 | + { |
| 104 | + g1 = SM9Pairing.pairing(peerR, de); // e(RA, deB) |
| 105 | + g2 = gPP.powSecure(r); // e(P_pub-e,P2)^rB |
| 106 | + g3 = g1.powSecure(r); |
| 107 | + } |
| 108 | + |
| 109 | + idA = initiator ? key.getIdentity() : peerIdentity; |
| 110 | + idB = initiator ? peerIdentity : key.getIdentity(); |
| 111 | + ECPoint ra = initiator ? ephemeralPoint : peerR; |
| 112 | + ECPoint rb = initiator ? peerR : ephemeralPoint; |
| 113 | + raBytes = SM9Curve.g1ToBytes(ra); |
| 114 | + rbBytes = SM9Curve.g1ToBytes(rb); |
| 115 | + |
| 116 | + ByteArrayOutputStream z = new ByteArrayOutputStream(); |
| 117 | + write(z, idA); |
| 118 | + write(z, idB); |
| 119 | + write(z, raBytes); |
| 120 | + write(z, rbBytes); |
| 121 | + write(z, SM9Pairing.toBytes(g1)); |
| 122 | + write(z, SM9Pairing.toBytes(g2)); |
| 123 | + write(z, SM9Pairing.toBytes(g3)); |
| 124 | + return SM9Sm3.kdf(z.toByteArray(), klenBits); |
| 125 | + } |
| 126 | + |
| 127 | + /** |
| 128 | + * S_B = Hash(0x82 || g1 || Hash(g2||g3||IDA||IDB||RA||RB)): the confirmation |
| 129 | + * the responder sends to (and the initiator checks against) the initiator. |
| 130 | + * <p> |
| 131 | + * The returned tag is a secret authenticator; a received value must be compared |
| 132 | + * against it with {@link org.bouncycastle.util.Arrays#constantTimeAreEqual(byte[], byte[])}, |
| 133 | + * not {@code Arrays.equals}, to avoid a timing side channel. |
| 134 | + */ |
| 135 | + public byte[] getResponderConfirmation() |
| 136 | + { |
| 137 | + return confirmation((byte)0x82); |
| 138 | + } |
| 139 | + |
| 140 | + /** |
| 141 | + * S_A = Hash(0x83 || g1 || Hash(g2||g3||IDA||IDB||RA||RB)): the confirmation |
| 142 | + * the initiator sends to (and the responder checks against) the responder. |
| 143 | + * <p> |
| 144 | + * The returned tag is a secret authenticator; a received value must be compared |
| 145 | + * against it with {@link org.bouncycastle.util.Arrays#constantTimeAreEqual(byte[], byte[])}, |
| 146 | + * not {@code Arrays.equals}, to avoid a timing side channel. |
| 147 | + */ |
| 148 | + public byte[] getInitiatorConfirmation() |
| 149 | + { |
| 150 | + return confirmation((byte)0x83); |
| 151 | + } |
| 152 | + |
| 153 | + private byte[] confirmation(byte tag) |
| 154 | + { |
| 155 | + if (g1 == null) |
| 156 | + { |
| 157 | + throw new IllegalStateException("calculateKey must be called first"); |
| 158 | + } |
| 159 | + SM3Digest sm3 = new SM3Digest(); |
| 160 | + update(sm3, SM9Pairing.toBytes(g2)); |
| 161 | + update(sm3, SM9Pairing.toBytes(g3)); |
| 162 | + update(sm3, idA); |
| 163 | + update(sm3, idB); |
| 164 | + update(sm3, raBytes); |
| 165 | + update(sm3, rbBytes); |
| 166 | + byte[] inner = new byte[32]; |
| 167 | + sm3.doFinal(inner, 0); |
| 168 | + |
| 169 | + sm3.update(tag); |
| 170 | + update(sm3, SM9Pairing.toBytes(g1)); |
| 171 | + update(sm3, inner); |
| 172 | + byte[] out = new byte[32]; |
| 173 | + sm3.doFinal(out, 0); |
| 174 | + return out; |
| 175 | + } |
| 176 | + |
| 177 | + private static void write(ByteArrayOutputStream out, byte[] b) |
| 178 | + { |
| 179 | + out.write(b, 0, b.length); |
| 180 | + } |
| 181 | + |
| 182 | + private static void update(SM3Digest sm3, byte[] b) |
| 183 | + { |
| 184 | + sm3.update(b, 0, b.length); |
| 185 | + } |
| 186 | +} |
0 commit comments