|
| 1 | +using System; |
| 2 | +using IntXLib; |
| 3 | +using System.Text; |
| 4 | + |
| 5 | +namespace ECDH |
| 6 | +{ |
| 7 | + public class EllipticDiffieHellman |
| 8 | + { |
| 9 | + protected static readonly Random rand = new Random(); |
| 10 | + |
| 11 | + protected readonly EllipticCurve curve; |
| 12 | + public readonly IntX priv; |
| 13 | + protected readonly Point generator, pub; |
| 14 | + |
| 15 | + |
| 16 | + public EllipticDiffieHellman(EllipticCurve curve, Point generator, IntX order, byte[] priv = null) |
| 17 | + { |
| 18 | + this.curve = curve; |
| 19 | + this.generator = generator; |
| 20 | + |
| 21 | + // Generate private key |
| 22 | + if (priv == null) |
| 23 | + { |
| 24 | + byte[] max = order.ToArray(); |
| 25 | + do |
| 26 | + { |
| 27 | + byte[] p1 = new byte[5 /*rand.Next(max.Length) + 1*/]; |
| 28 | + |
| 29 | + rand.NextBytes(p1); |
| 30 | + |
| 31 | + if (p1.Length == max.Length) p1[p1.Length - 1] %= max[max.Length - 1]; |
| 32 | + else p1[p1.Length - 1] &= 127; |
| 33 | + |
| 34 | + this.priv = Helper.FromArray(p1); |
| 35 | + } while (this.priv<2); |
| 36 | + } |
| 37 | + else this.priv = Helper.FromArray(priv); |
| 38 | + |
| 39 | + // Generate public key |
| 40 | + pub = curve.Multiply(generator, this.priv); |
| 41 | + } |
| 42 | + |
| 43 | + public byte[] GetPublicKey() |
| 44 | + { |
| 45 | + byte[] p1 = pub.X.ToArray(); |
| 46 | + byte[] p2 = pub.Y.ToArray(); |
| 47 | + |
| 48 | + byte[] ser = new byte[4 + p1.Length + p2.Length]; |
| 49 | + ser[0] = (byte)(p1.Length & 255); |
| 50 | + ser[1] = (byte)((p1.Length >> 8) & 255); |
| 51 | + ser[2] = (byte)((p1.Length >> 16) & 255); |
| 52 | + ser[3] = (byte)((p1.Length >> 24) & 255); |
| 53 | + Array.Copy(p1, 0, ser, 4, p1.Length); |
| 54 | + Array.Copy(p2, 0, ser, 4 + p1.Length, p2.Length); |
| 55 | + |
| 56 | + return ser; |
| 57 | + } |
| 58 | + |
| 59 | + public byte[] GetPrivateKey() => priv.ToArray(); |
| 60 | + |
| 61 | + public byte[] GetSharedSecret(byte[] pK) |
| 62 | + { |
| 63 | + byte[] p1 = new byte[pK[0] | (pK[1]<<8) | (pK[2]<<16) | (pK[3]<<24)]; // Reconstruct x-axis size |
| 64 | + byte[] p2 = new byte[pK.Length - p1.Length - 4]; |
| 65 | + Array.Copy(pK, 4, p1, 0, p1.Length); |
| 66 | + Array.Copy(pK, 4 + p1.Length, p2, 0, p2.Length); |
| 67 | + |
| 68 | + Point remotePublic = new Point(Helper.FromArray(p1), Helper.FromArray(p2)); |
| 69 | + |
| 70 | + byte[] secret = curve.Multiply(remotePublic, priv).X.ToArray(); // Use the x-coordinate as the shared secret |
| 71 | + |
| 72 | + // PBKDF2-HMAC-SHA1 (Common shared secret generation method) |
| 73 | + return PBKDF2(HMAC_SHA1, secret, Encoding.UTF8.GetBytes("P1sN0R4inb0wPl5P1sPls"), 1024, 32); |
| 74 | + } |
| 75 | + |
| 76 | + |
| 77 | + public delegate byte[] PRF(byte[] key, byte[] salt); |
| 78 | + private static byte[] PBKDF2(PRF function, byte[] password, byte[] salt, int iterations, int dklen) |
| 79 | + { |
| 80 | + byte[] dk = new byte[0]; // Create a placeholder for the derived key |
| 81 | + uint iter = 1; // Track the iterations |
| 82 | + while (dk.Length < dklen) |
| 83 | + { |
| 84 | + // F-function |
| 85 | + // The F-function (PRF) takes the amount of iterations performed in the opposite endianness format from what C# uses, so we have to swap the endianness |
| 86 | + byte[] u = function(password, Concatenate(salt, WriteToArray(new byte[4], SwapEndian(iter), 0))); |
| 87 | + byte[] ures = new byte[u.Length]; |
| 88 | + Array.Copy(u, ures, u.Length); |
| 89 | + for (int i = 1; i < iterations; ++i) |
| 90 | + { |
| 91 | + // Iteratively apply the PRF |
| 92 | + u = function(password, u); |
| 93 | + for (int j = 0; j < u.Length; ++j) ures[j] ^= u[j]; |
| 94 | + } |
| 95 | + |
| 96 | + // Concatenate the result to the dk |
| 97 | + dk = Concatenate(dk, ures); |
| 98 | + |
| 99 | + ++iter; |
| 100 | + } |
| 101 | + |
| 102 | + // Clip all bytes past what we needed (yes, that's really what the standard is) |
| 103 | + if (dk.Length != dklen) |
| 104 | + { |
| 105 | + var t1 = new byte[dklen]; |
| 106 | + Array.Copy(dk, t1, Math.Min(dklen, dk.Length)); |
| 107 | + return t1; |
| 108 | + } |
| 109 | + return dk; |
| 110 | + } |
| 111 | + public delegate byte[] HashFunction(byte[] message); |
| 112 | + private static byte[] HMAC(byte[] key, byte[] message, HashFunction func, int blockSizeBytes) |
| 113 | + { |
| 114 | + if (key.Length > blockSizeBytes) key = func(key); |
| 115 | + else if (key.Length < blockSizeBytes) |
| 116 | + { |
| 117 | + byte[] b = new byte[blockSizeBytes]; |
| 118 | + Array.Copy(key, b, key.Length); |
| 119 | + key = b; |
| 120 | + } |
| 121 | + |
| 122 | + byte[] o_key_pad = new byte[blockSizeBytes]; // Outer padding |
| 123 | + byte[] i_key_pad = new byte[blockSizeBytes]; // Inner padding |
| 124 | + for (int i = 0; i < blockSizeBytes; ++i) |
| 125 | + { |
| 126 | + // Combine padding with key |
| 127 | + o_key_pad[i] = (byte)(key[i] ^ 0x5c); |
| 128 | + i_key_pad[i] = (byte)(key[i] ^ 0x36); |
| 129 | + } |
| 130 | + return func(Concatenate(o_key_pad, func(Concatenate(message, i_key_pad)))); |
| 131 | + } |
| 132 | + private static byte[] HMAC_SHA1(byte[] key, byte[] message) => HMAC(key, message, SHA1, 20); |
| 133 | + private static byte[] Concatenate(params byte[][] bytes) |
| 134 | + { |
| 135 | + int alloc = 0; |
| 136 | + foreach (byte[] b in bytes) alloc += b.Length; |
| 137 | + byte[] result = new byte[alloc]; |
| 138 | + alloc = 0; |
| 139 | + for (int i = 0; i < bytes.Length; ++i) |
| 140 | + { |
| 141 | + Array.Copy(bytes[i], 0, result, alloc, bytes[i].Length); |
| 142 | + alloc += bytes[i].Length; |
| 143 | + } |
| 144 | + return result; |
| 145 | + } |
| 146 | + public static byte[] SHA1(byte[] message) |
| 147 | + { |
| 148 | + // Initialize buffers |
| 149 | + uint h0 = 0x67452301; |
| 150 | + uint h1 = 0xEFCDAB89; |
| 151 | + uint h2 = 0x98BADCFE; |
| 152 | + uint h3 = 0x10325476; |
| 153 | + uint h4 = 0xC3D2E1F0; |
| 154 | + |
| 155 | + // Pad message |
| 156 | + int ml = message.Length + 1; |
| 157 | + byte[] msg = new byte[ml + ((960 - (ml * 8 % 512)) % 512) / 8 + 8]; |
| 158 | + Array.Copy(message, msg, message.Length); |
| 159 | + msg[message.Length] = 0x80; |
| 160 | + long len = message.Length * 8; |
| 161 | + for (int i = 0; i < 8; ++i) msg[msg.Length - 1 - i] = (byte)((len >> (i * 8)) & 255); |
| 162 | + //Support.WriteToArray(msg, message.Length * 8, msg.Length - 8); |
| 163 | + //for (int i = 0; i <4; ++i) msg[msg.Length - 5 - i] = (byte)(((message.Length*8) >> (i * 8)) & 255); |
| 164 | + |
| 165 | + int chunks = msg.Length / 64; |
| 166 | + |
| 167 | + // Perform hashing for each 512-bit block |
| 168 | + for (int i = 0; i < chunks; ++i) |
| 169 | + { |
| 170 | + |
| 171 | + // Split block into words |
| 172 | + uint[] w = new uint[80]; |
| 173 | + for (int j = 0; j < 16; ++j) |
| 174 | + w[j] |= (uint)((msg[i * 64 + j * 4] << 24) | (msg[i * 64 + j * 4 + 1] << 16) | (msg[i * 64 + j * 4 + 2] << 8) | (msg[i * 64 + j * 4 + 3] << 0)); |
| 175 | + |
| 176 | + // Expand words |
| 177 | + for (int j = 16; j < 80; ++j) |
| 178 | + w[j] = Rot(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1); |
| 179 | + |
| 180 | + // Initialize chunk-hash |
| 181 | + uint |
| 182 | + a = h0, |
| 183 | + b = h1, |
| 184 | + c = h2, |
| 185 | + d = h3, |
| 186 | + e = h4; |
| 187 | + |
| 188 | + // Do hash rounds |
| 189 | + for (int t = 0; t < 80; ++t) |
| 190 | + { |
| 191 | + uint tmp = ((a << 5) | (a >> (27))) + |
| 192 | + ( // Round-function |
| 193 | + t < 20 ? (b & c) | ((~b) & d) : |
| 194 | + t < 40 ? b ^ c ^ d : |
| 195 | + t < 60 ? (b & c) | (b & d) | (c & d) : |
| 196 | + /*t<80*/ b ^ c ^ d |
| 197 | + ) + |
| 198 | + e + |
| 199 | + ( // K-function |
| 200 | + t < 20 ? 0x5A827999 : |
| 201 | + t < 40 ? 0x6ED9EBA1 : |
| 202 | + t < 60 ? 0x8F1BBCDC : |
| 203 | + /*t<80*/ 0xCA62C1D6 |
| 204 | + ) + |
| 205 | + w[t]; |
| 206 | + e = d; |
| 207 | + d = c; |
| 208 | + c = Rot(b, 30); |
| 209 | + b = a; |
| 210 | + a = tmp; |
| 211 | + } |
| 212 | + h0 += a; |
| 213 | + h1 += b; |
| 214 | + h2 += c; |
| 215 | + h3 += d; |
| 216 | + h4 += e; |
| 217 | + } |
| 218 | + |
| 219 | + return WriteContiguous(new byte[20], 0, SwapEndian(h0), SwapEndian(h1), SwapEndian(h2), SwapEndian(h3), SwapEndian(h4)); |
| 220 | + } |
| 221 | + |
| 222 | + private static uint Rot(uint val, int by) => (val << by) | (val >> (32 - by)); |
| 223 | + |
| 224 | + // Swap endianness of a given integer |
| 225 | + private static uint SwapEndian(uint value) => (uint)(((value >> 24) & (255 << 0)) | ((value >> 8) & (255 << 8)) | ((value << 8) & (255 << 16)) | ((value << 24) & (255 << 24))); |
| 226 | + |
| 227 | + private static byte[] WriteToArray(byte[] target, uint data, int offset) |
| 228 | + { |
| 229 | + for (int i = 0; i < 4; ++i) |
| 230 | + target[i + offset] = (byte)((data >> (i * 8)) & 255); |
| 231 | + return target; |
| 232 | + } |
| 233 | + |
| 234 | + private static byte[] WriteContiguous(byte[] target, int offset, params uint[] data) |
| 235 | + { |
| 236 | + for (int i = 0; i < data.Length; ++i) WriteToArray(target, data[i], offset + i * 4); |
| 237 | + return target; |
| 238 | + } |
| 239 | + } |
| 240 | + |
| 241 | + public static class Helper |
| 242 | + { |
| 243 | + public static byte[] ToArray(this IntX v) |
| 244 | + { |
| 245 | + v.GetInternalState(out uint[] digits, out bool negative); |
| 246 | + byte[] b = DigitConverter.ToBytes(digits); |
| 247 | + byte[] b1 = new byte[b.Length]; |
| 248 | + Array.Copy(b, b1, b.Length); |
| 249 | + b1[b.Length] = (byte)(negative ? 1 : 0); |
| 250 | + return b1; |
| 251 | + } |
| 252 | + public static IntX FromArray(byte[] b) |
| 253 | + { |
| 254 | + if (b.Length == 0) return new IntX(); |
| 255 | + byte[] b1 = new byte[b.Length - 1]; |
| 256 | + Array.Copy(b, b1, b1.Length); |
| 257 | + uint[] u = DigitConverter.FromBytes(b1); |
| 258 | + return new IntX(u, b[b.Length - 1]==1); |
| 259 | + } |
| 260 | + public static bool BitAt(this uint[] data, long index) => (data[index/8]&(1<<(int)(index%8)))!=0; |
| 261 | + public static IntX Abs(this IntX i) => i < 0 ? -i : i; |
| 262 | + } |
| 263 | +} |
0 commit comments