JPakeUtilities.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
  2. #pragma warning disable
  3. using System;
  4. using System.Text;
  5. using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto;
  6. using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Macs;
  7. using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Parameters;
  8. using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Utilities;
  9. using BestHTTP.SecureProtocol.Org.BouncyCastle.Math;
  10. using BestHTTP.SecureProtocol.Org.BouncyCastle.Security;
  11. using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
  12. namespace BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Agreement.JPake
  13. {
  14. /// <summary>
  15. /// Primitives needed for a J-PAKE exchange.
  16. ///
  17. /// The recommended way to perform a J-PAKE exchange is by using
  18. /// two JPAKEParticipants. Internally, those participants
  19. /// call these primitive operations in JPakeUtilities.
  20. ///
  21. /// The primitives, however, can be used without a JPAKEParticipant if needed.
  22. /// </summary>
  23. public abstract class JPakeUtilities
  24. {
  25. public static readonly BigInteger Zero = BigInteger.Zero;
  26. public static readonly BigInteger One = BigInteger.One;
  27. /// <summary>
  28. /// Return a value that can be used as x1 or x3 during round 1.
  29. /// The returned value is a random value in the range [0, q-1].
  30. /// </summary>
  31. public static BigInteger GenerateX1(BigInteger q, SecureRandom random)
  32. {
  33. BigInteger min = Zero;
  34. BigInteger max = q.Subtract(One);
  35. return BigIntegers.CreateRandomInRange(min, max, random);
  36. }
  37. /// <summary>
  38. /// Return a value that can be used as x2 or x4 during round 1.
  39. /// The returned value is a random value in the range [1, q-1].
  40. /// </summary>
  41. public static BigInteger GenerateX2(BigInteger q, SecureRandom random)
  42. {
  43. BigInteger min = One;
  44. BigInteger max = q.Subtract(One);
  45. return BigIntegers.CreateRandomInRange(min, max, random);
  46. }
  47. /// <summary>
  48. /// Converts the given password to a BigInteger
  49. /// for use in arithmetic calculations.
  50. /// </summary>
  51. public static BigInteger CalculateS(char[] password)
  52. {
  53. return new BigInteger(Encoding.UTF8.GetBytes(password));
  54. }
  55. /// <summary>
  56. /// Calculate g^x mod p as done in round 1.
  57. /// </summary>
  58. public static BigInteger CalculateGx(BigInteger p, BigInteger g, BigInteger x)
  59. {
  60. return g.ModPow(x, p);
  61. }
  62. /// <summary>
  63. /// Calculate ga as done in round 2.
  64. /// </summary>
  65. public static BigInteger CalculateGA(BigInteger p, BigInteger gx1, BigInteger gx3, BigInteger gx4)
  66. {
  67. // ga = g^(x1+x3+x4) = g^x1 * g^x3 * g^x4
  68. return gx1.Multiply(gx3).Multiply(gx4).Mod(p);
  69. }
  70. /// <summary>
  71. /// Calculate x2 * s as done in round 2.
  72. /// </summary>
  73. public static BigInteger CalculateX2s(BigInteger q, BigInteger x2, BigInteger s)
  74. {
  75. return x2.Multiply(s).Mod(q);
  76. }
  77. /// <summary>
  78. /// Calculate A as done in round 2.
  79. /// </summary>
  80. public static BigInteger CalculateA(BigInteger p, BigInteger q, BigInteger gA, BigInteger x2s)
  81. {
  82. // A = ga^(x*s)
  83. return gA.ModPow(x2s, p);
  84. }
  85. /// <summary>
  86. /// Calculate a zero knowledge proof of x using Schnorr's signature.
  87. /// The returned array has two elements {g^v, r = v-x*h} for x.
  88. /// </summary>
  89. public static BigInteger[] CalculateZeroKnowledgeProof(BigInteger p, BigInteger q, BigInteger g,
  90. BigInteger gx, BigInteger x, string participantId, IDigest digest, SecureRandom random)
  91. {
  92. /* Generate a random v, and compute g^v */
  93. BigInteger vMin = Zero;
  94. BigInteger vMax = q.Subtract(One);
  95. BigInteger v = BigIntegers.CreateRandomInRange(vMin, vMax, random);
  96. BigInteger gv = g.ModPow(v, p);
  97. BigInteger h = CalculateHashForZeroKnowledgeProof(g, gv, gx, participantId, digest); // h
  98. return new BigInteger[]
  99. {
  100. gv,
  101. v.Subtract(x.Multiply(h)).Mod(q) // r = v-x*h
  102. };
  103. }
  104. private static BigInteger CalculateHashForZeroKnowledgeProof(BigInteger g, BigInteger gr, BigInteger gx,
  105. string participantId, IDigest digest)
  106. {
  107. digest.Reset();
  108. UpdateDigestIncludingSize(digest, g);
  109. UpdateDigestIncludingSize(digest, gr);
  110. UpdateDigestIncludingSize(digest, gx);
  111. UpdateDigestIncludingSize(digest, participantId);
  112. byte[] output = DigestUtilities.DoFinal(digest);
  113. return new BigInteger(output);
  114. }
  115. /// <summary>
  116. /// Validates that g^x4 is not 1.
  117. /// throws CryptoException if g^x4 is 1
  118. /// </summary>
  119. public static void ValidateGx4(BigInteger gx4)
  120. {
  121. if (gx4.Equals(One))
  122. throw new CryptoException("g^x validation failed. g^x should not be 1.");
  123. }
  124. /// <summary>
  125. /// Validates that ga is not 1.
  126. ///
  127. /// As described by Feng Hao...
  128. /// Alice could simply check ga != 1 to ensure it is a generator.
  129. /// In fact, as we will explain in Section 3, (x1 + x3 + x4 ) is random over Zq even in the face of active attacks.
  130. /// Hence, the probability for ga = 1 is extremely small - on the order of 2^160 for 160-bit q.
  131. ///
  132. /// throws CryptoException if ga is 1
  133. /// </summary>
  134. public static void ValidateGa(BigInteger ga)
  135. {
  136. if (ga.Equals(One))
  137. throw new CryptoException("ga is equal to 1. It should not be. The chances of this happening are on the order of 2^160 for a 160-bit q. Try again.");
  138. }
  139. /// <summary>
  140. /// Validates the zero knowledge proof (generated by
  141. /// calculateZeroKnowledgeProof(BigInteger, BigInteger, BigInteger, BigInteger, BigInteger, string, Digest, SecureRandom)
  142. /// is correct.
  143. ///
  144. /// throws CryptoException if the zero knowledge proof is not correct
  145. /// </summary>
  146. public static void ValidateZeroKnowledgeProof(BigInteger p, BigInteger q, BigInteger g,
  147. BigInteger gx, BigInteger[] zeroKnowledgeProof, string participantId, IDigest digest)
  148. {
  149. /* sig={g^v,r} */
  150. BigInteger gv = zeroKnowledgeProof[0];
  151. BigInteger r = zeroKnowledgeProof[1];
  152. BigInteger h = CalculateHashForZeroKnowledgeProof(g, gv, gx, participantId, digest);
  153. if (!(gx.CompareTo(Zero) == 1 && // g^x > 0
  154. gx.CompareTo(p) == -1 && // g^x < p
  155. gx.ModPow(q, p).CompareTo(One) == 0 && // g^x^q mod q = 1
  156. /*
  157. * Below, I took a straightforward way to compute g^r * g^x^h,
  158. * which needs 2 exp. Using a simultaneous computation technique
  159. * would only need 1 exp.
  160. */
  161. g.ModPow(r, p).Multiply(gx.ModPow(h, p)).Mod(p).CompareTo(gv) == 0)) // g^v=g^r * g^x^h
  162. {
  163. throw new CryptoException("Zero-knowledge proof validation failed");
  164. }
  165. }
  166. /// <summary>
  167. /// Calculates the keying material, which can be done after round 2 has completed.
  168. /// A session key must be derived from this key material using a secure key derivation function (KDF).
  169. /// The KDF used to derive the key is handled externally (i.e. not by JPAKEParticipant).
  170. ///
  171. /// KeyingMaterial = (B/g^{x2*x4*s})^x2
  172. /// </summary>
  173. public static BigInteger CalculateKeyingMaterial(BigInteger p, BigInteger q,
  174. BigInteger gx4, BigInteger x2, BigInteger s, BigInteger B)
  175. {
  176. return gx4.ModPow(x2.Multiply(s).Negate().Mod(q), p).Multiply(B).ModPow(x2, p);
  177. }
  178. /// <summary>
  179. /// Validates that the given participant ids are not equal.
  180. /// (For the J-PAKE exchange, each participant must use a unique id.)
  181. ///
  182. /// Throws CryptoException if the participantId strings are equal.
  183. /// </summary>
  184. public static void ValidateParticipantIdsDiffer(string participantId1, string participantId2)
  185. {
  186. if (participantId1.Equals(participantId2))
  187. {
  188. throw new CryptoException(
  189. "Both participants are using the same participantId ("
  190. + participantId1
  191. + "). This is not allowed. "
  192. + "Each participant must use a unique participantId.");
  193. }
  194. }
  195. /// <summary>
  196. /// Validates that the given participant ids are equal.
  197. /// This is used to ensure that the payloads received from
  198. /// each round all come from the same participant.
  199. /// </summary>
  200. public static void ValidateParticipantIdsEqual(string expectedParticipantId, string actualParticipantId)
  201. {
  202. if (!expectedParticipantId.Equals(actualParticipantId))
  203. {
  204. throw new CryptoException(
  205. "Received payload from incorrect partner ("
  206. + actualParticipantId
  207. + "). Expected to receive payload from "
  208. + expectedParticipantId
  209. + ".");
  210. }
  211. }
  212. /// <summary>
  213. /// Validates that the given object is not null.
  214. /// throws NullReferenceException if the object is null.
  215. /// </summary>
  216. /// <param name="obj">object in question</param>
  217. /// <param name="description">name of the object (to be used in exception message)</param>
  218. public static void ValidateNotNull(object obj, string description)
  219. {
  220. if (obj == null)
  221. throw new ArgumentNullException(description);
  222. }
  223. /// <summary>
  224. /// Calculates the MacTag (to be used for key confirmation), as defined by
  225. /// <a href="http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf">NIST SP 800-56A Revision 1</a>,
  226. /// Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes.
  227. ///
  228. /// MacTag = HMAC(MacKey, MacLen, MacData)
  229. /// MacKey = H(K || "JPAKE_KC")
  230. /// MacData = "KC_1_U" || participantId || partnerParticipantId || gx1 || gx2 || gx3 || gx4
  231. ///
  232. /// Note that both participants use "KC_1_U" because the sender of the round 3 message
  233. /// is always the initiator for key confirmation.
  234. ///
  235. /// HMAC = {@link HMac} used with the given {@link Digest}
  236. /// H = The given {@link Digest}
  237. /// MacLen = length of MacTag
  238. /// </summary>
  239. public static BigInteger CalculateMacTag(string participantId, string partnerParticipantId,
  240. BigInteger gx1, BigInteger gx2, BigInteger gx3, BigInteger gx4, BigInteger keyingMaterial, IDigest digest)
  241. {
  242. byte[] macKey = CalculateMacKey(keyingMaterial, digest);
  243. HMac mac = new HMac(digest);
  244. mac.Init(new KeyParameter(macKey));
  245. Arrays.Fill(macKey, (byte)0);
  246. /*
  247. * MacData = "KC_1_U" || participantId_Alice || participantId_Bob || gx1 || gx2 || gx3 || gx4.
  248. */
  249. UpdateMac(mac, "KC_1_U");
  250. UpdateMac(mac, participantId);
  251. UpdateMac(mac, partnerParticipantId);
  252. UpdateMac(mac, gx1);
  253. UpdateMac(mac, gx2);
  254. UpdateMac(mac, gx3);
  255. UpdateMac(mac, gx4);
  256. byte[] macOutput = MacUtilities.DoFinal(mac);
  257. return new BigInteger(macOutput);
  258. }
  259. /// <summary>
  260. /// Calculates the MacKey (i.e. the key to use when calculating the MagTag for key confirmation).
  261. ///
  262. /// MacKey = H(K || "JPAKE_KC")
  263. /// </summary>
  264. private static byte[] CalculateMacKey(BigInteger keyingMaterial, IDigest digest)
  265. {
  266. digest.Reset();
  267. UpdateDigest(digest, keyingMaterial);
  268. /*
  269. * This constant is used to ensure that the macKey is NOT the same as the derived key.
  270. */
  271. UpdateDigest(digest, "JPAKE_KC");
  272. return DigestUtilities.DoFinal(digest);
  273. }
  274. /// <summary>
  275. /// Validates the MacTag received from the partner participant.
  276. ///
  277. /// throws CryptoException if the participantId strings are equal.
  278. /// </summary>
  279. public static void ValidateMacTag(string participantId, string partnerParticipantId,
  280. BigInteger gx1, BigInteger gx2, BigInteger gx3, BigInteger gx4,
  281. BigInteger keyingMaterial, IDigest digest, BigInteger partnerMacTag)
  282. {
  283. /*
  284. * Calculate the expected MacTag using the parameters as the partner
  285. * would have used when the partner called calculateMacTag.
  286. *
  287. * i.e. basically all the parameters are reversed.
  288. * participantId <-> partnerParticipantId
  289. * x1 <-> x3
  290. * x2 <-> x4
  291. */
  292. BigInteger expectedMacTag = CalculateMacTag(partnerParticipantId, participantId, gx3, gx4, gx1, gx2, keyingMaterial, digest);
  293. if (!expectedMacTag.Equals(partnerMacTag))
  294. {
  295. throw new CryptoException(
  296. "Partner MacTag validation failed. "
  297. + "Therefore, the password, MAC, or digest algorithm of each participant does not match.");
  298. }
  299. }
  300. private static void UpdateDigest(IDigest digest, BigInteger bigInteger)
  301. {
  302. UpdateDigest(digest, BigIntegers.AsUnsignedByteArray(bigInteger));
  303. }
  304. private static void UpdateDigest(IDigest digest, string str)
  305. {
  306. UpdateDigest(digest, Encoding.UTF8.GetBytes(str));
  307. }
  308. private static void UpdateDigest(IDigest digest, byte[] bytes)
  309. {
  310. digest.BlockUpdate(bytes, 0, bytes.Length);
  311. Arrays.Fill(bytes, (byte)0);
  312. }
  313. private static void UpdateDigestIncludingSize(IDigest digest, BigInteger bigInteger)
  314. {
  315. UpdateDigestIncludingSize(digest, BigIntegers.AsUnsignedByteArray(bigInteger));
  316. }
  317. private static void UpdateDigestIncludingSize(IDigest digest, string str)
  318. {
  319. UpdateDigestIncludingSize(digest, Encoding.UTF8.GetBytes(str));
  320. }
  321. private static void UpdateDigestIncludingSize(IDigest digest, byte[] bytes)
  322. {
  323. digest.BlockUpdate(IntToByteArray(bytes.Length), 0, 4);
  324. digest.BlockUpdate(bytes, 0, bytes.Length);
  325. Arrays.Fill(bytes, (byte)0);
  326. }
  327. private static void UpdateMac(IMac mac, BigInteger bigInteger)
  328. {
  329. UpdateMac(mac, BigIntegers.AsUnsignedByteArray(bigInteger));
  330. }
  331. private static void UpdateMac(IMac mac, string str)
  332. {
  333. UpdateMac(mac, Encoding.UTF8.GetBytes(str));
  334. }
  335. private static void UpdateMac(IMac mac, byte[] bytes)
  336. {
  337. mac.BlockUpdate(bytes, 0, bytes.Length);
  338. Arrays.Fill(bytes, (byte)0);
  339. }
  340. private static byte[] IntToByteArray(int value)
  341. {
  342. return Pack.UInt32_To_BE((uint)value);
  343. }
  344. }
  345. }
  346. #pragma warning restore
  347. #endif