1 /** 2 * ElGamal 3 * 4 * Copyright: 5 * (C) 1999-2007 Jack Lloyd 6 * (C) 2014-2015 Etienne Cimon 7 * 8 * License: 9 * Botan is released under the Simplified BSD License (see LICENSE.md) 10 */ 11 module botan.pubkey.algo.elgamal; 12 13 import botan.constants; 14 static if (BOTAN_HAS_PUBLIC_KEY_CRYPTO && BOTAN_HAS_ELGAMAL): 15 16 public import botan.pubkey.pubkey; 17 import botan.pubkey.algo.dl_algo; 18 import botan.math.numbertheory.numthry; 19 import botan.math.numbertheory.reducer; 20 import botan.pubkey.blinding; 21 import botan.pubkey.pk_ops; 22 import botan.pubkey.workfactor; 23 import botan.math.numbertheory.numthry; 24 import botan.pubkey.algo.keypair; 25 import botan.rng.rng; 26 import botan.utils.types; 27 import memutils.helpers : Embed; 28 29 struct ElGamalOptions { 30 enum algoName = "ElGamal"; 31 enum format = DLGroup.ANSI_X9_42; 32 33 /* 34 * Check Private ElGamal Parameters 35 */ 36 static bool checkKey(in DLSchemePrivateKey privkey, RandomNumberGenerator rng, bool strong) 37 { 38 if (!privkey.checkKeyImpl(rng, strong)) 39 return false; 40 41 if (!strong) 42 return true; 43 44 return encryptionConsistencyCheck(rng, privkey, "EME1(SHA-1)"); 45 } 46 47 } 48 49 /** 50 * ElGamal Public Key 51 */ 52 struct ElGamalPublicKey 53 { 54 public: 55 alias Options = ElGamalOptions; 56 __gshared immutable string algoName = Options.algoName; 57 58 this(in AlgorithmIdentifier alg_id, const ref SecureVector!ubyte key_bits) 59 { 60 m_owned = true; 61 m_pub = new DLSchemePublicKey(Options(), alg_id, key_bits); 62 } 63 /* 64 * ElGamalPublicKey Constructor 65 */ 66 this(DLGroup grp, BigInt y1) 67 { 68 m_owned = true; 69 m_pub = new DLSchemePublicKey(Options(), grp.move, y1.move); 70 } 71 72 this(PublicKey pkey) { m_pub = cast(DLSchemePublicKey) pkey; } 73 this(PrivateKey pkey) { m_pub = cast(DLSchemePublicKey) pkey; } 74 75 mixin Embed!(m_pub, m_owned); 76 77 bool m_owned; 78 DLSchemePublicKey m_pub; 79 } 80 81 /** 82 * ElGamal Private Key 83 */ 84 struct ElGamalPrivateKey 85 { 86 public: 87 alias Options = ElGamalOptions; 88 __gshared immutable string algoName = Options.algoName; 89 90 /* 91 * ElGamalPrivateKey Constructor 92 */ 93 this(RandomNumberGenerator rng, DLGroup grp, BigInt x_arg = BigInt(0)) 94 { 95 bool x_arg_0; 96 if (x_arg == 0) { 97 x_arg_0 = true; 98 x_arg.randomize(rng, 2 * dlWorkFactor(grp.getP().bits())); 99 } 100 BigInt y1 = powerMod(grp.getG(), x_arg, grp.getP()); 101 102 m_owned = true; 103 m_priv = new DLSchemePrivateKey(Options(), grp.move, y1.move, x_arg.move); 104 105 if (x_arg_0) 106 m_priv.genCheck(rng); 107 else 108 m_priv.loadCheck(rng); 109 110 } 111 112 this(in AlgorithmIdentifier alg_id, 113 const ref SecureVector!ubyte key_bits, 114 RandomNumberGenerator rng) 115 { 116 m_owned = true; 117 m_priv = new DLSchemePrivateKey(Options(), alg_id, key_bits); 118 119 m_priv.setY(powerMod(m_priv.groupG(), m_priv.m_x, m_priv.groupP())); 120 m_priv.loadCheck(rng); 121 } 122 123 this(PrivateKey pkey) { m_priv = cast(DLSchemePrivateKey) pkey; } 124 125 mixin Embed!(m_priv, m_owned); 126 127 bool m_owned; 128 DLSchemePrivateKey m_priv; 129 } 130 131 /** 132 * ElGamal encryption operation 133 */ 134 final class ElGamalEncryptionOperation : Encryption 135 { 136 public: 137 override size_t maxInputBits() const { return m_mod_p.getModulus().bits() - 1; } 138 139 this(in PublicKey pkey) { 140 this(cast(DLSchemePublicKey) pkey); 141 } 142 143 this(in ElGamalPublicKey pkey) { 144 this(pkey.m_pub); 145 } 146 147 this(in DLSchemePublicKey key) 148 { 149 assert(key.algoName == ElGamalPublicKey.algoName); 150 const BigInt* p = &key.groupP(); 151 152 m_powermod_g_p = FixedBasePowerMod(key.groupG(), *p); 153 m_powermod_y_p = FixedBasePowerMod(key.getY(), *p); 154 m_mod_p = ModularReducer(*p); 155 } 156 157 override SecureVector!ubyte encrypt(const(ubyte)* msg, size_t msg_len, RandomNumberGenerator rng) 158 { 159 const BigInt* p = &m_mod_p.getModulus(); 160 161 BigInt m = BigInt(msg, msg_len); 162 163 if (m >= *p) 164 throw new InvalidArgument("ElGamal encryption: Input is too large"); 165 166 BigInt k = BigInt(rng, 2 * dlWorkFactor(p.bits())); 167 168 BigInt a = (*m_powermod_g_p)(k); 169 BigInt b = m_mod_p.multiply(m, (*m_powermod_y_p)(k)); 170 171 SecureVector!ubyte output = SecureVector!ubyte(2*p.bytes()); 172 a.binaryEncode(&output[p.bytes() - a.bytes()]); 173 b.binaryEncode(&output[output.length / 2 + (p.bytes() - b.bytes())]); 174 return output; 175 } 176 177 private: 178 FixedBasePowerMod m_powermod_g_p, m_powermod_y_p; 179 ModularReducer m_mod_p; 180 } 181 182 /** 183 * ElGamal decryption operation 184 */ 185 final class ElGamalDecryptionOperation : Decryption 186 { 187 public: 188 override size_t maxInputBits() const { return m_mod_p.getModulus().bits() - 1; } 189 190 this(in PrivateKey pkey, RandomNumberGenerator rng) { 191 this(cast(DLSchemePrivateKey) pkey, rng); 192 } 193 194 this(in ElGamalPrivateKey key, RandomNumberGenerator rng) 195 { 196 this(key.m_priv, rng); 197 } 198 199 this(in DLSchemePrivateKey key, RandomNumberGenerator rng) 200 { 201 assert(key.algoName == ElGamalPublicKey.algoName); 202 const BigInt* p = &key.groupP(); 203 204 m_powermod_x_p = FixedExponentPowerMod(key.getX(), *p); 205 m_mod_p = ModularReducer(*p); 206 207 BigInt k = BigInt(rng, p.bits() - 1); 208 auto d = (*m_powermod_x_p)(k); 209 m_blinder = Blinder(k, d, *p); 210 } 211 212 override SecureVector!ubyte decrypt(const(ubyte)* msg, size_t msg_len) 213 { 214 const BigInt* p = &m_mod_p.getModulus(); 215 216 const size_t p_bytes = p.bytes(); 217 218 if (msg_len != 2 * p_bytes) 219 throw new InvalidArgument("ElGamal decryption: Invalid message"); 220 221 BigInt a = BigInt(msg, p_bytes); 222 BigInt b = BigInt(msg + p_bytes, p_bytes); 223 224 if (a >= *p || b >= *p) 225 throw new InvalidArgument("ElGamal decryption: Invalid message"); 226 227 a = m_blinder.blind(a); 228 229 BigInt r = m_mod_p.multiply(b, inverseMod((*m_powermod_x_p)(a), *p)); 230 231 return BigInt.encodeLocked(m_blinder.unblind(r)); 232 } 233 private: 234 FixedExponentPowerMod m_powermod_x_p; 235 ModularReducer m_mod_p; 236 Blinder m_blinder; 237 } 238 239 static if (BOTAN_TEST): 240 import botan.test; 241 import botan.pubkey.test; 242 import botan.pubkey.pubkey; 243 import botan.codec.hex; 244 import botan.pubkey.algo.dl_group; 245 import botan.rng.auto_rng; 246 import core.atomic; 247 import memutils.hashmap; 248 249 private shared size_t total_tests; 250 251 size_t testPkKeygen(RandomNumberGenerator rng) 252 { 253 size_t fails; 254 255 string[] elg_list = ["modp/ietf/1024", "dsa/jce/1024", "dsa/botan/2048", "dsa/botan/3072"]; 256 257 foreach (elg; elg_list) { 258 atomicOp!"+="(total_tests, 1); 259 auto key = ElGamalPrivateKey(rng, DLGroup(elg)); 260 key.checkKey(rng, true); 261 fails += validateSaveAndLoad(key, rng); 262 } 263 264 return fails; 265 } 266 267 size_t elgamalKat(string p, 268 string g, 269 string x, 270 string msg, 271 string padding, 272 string nonce, 273 string ciphertext) 274 { 275 atomicOp!"+="(total_tests, 1); 276 Unique!AutoSeededRNG rng = new AutoSeededRNG; 277 278 BigInt p_bn = BigInt(p); 279 BigInt g_bn = BigInt(g); 280 BigInt x_bn = BigInt(x); 281 282 DLGroup group = DLGroup(p_bn, g_bn); 283 auto privkey = ElGamalPrivateKey(*rng, group.move(), x_bn.move()); 284 285 auto pubkey = ElGamalPublicKey(privkey); 286 287 if (padding == "") 288 padding = "Raw"; 289 290 auto enc = scoped!PKEncryptorEME(pubkey, padding); 291 auto dec = scoped!PKDecryptorEME(privkey, padding); 292 293 return validateEncryption(enc, dec, "ElGamal/" ~ padding, msg, nonce, ciphertext); 294 } 295 296 static if (BOTAN_HAS_TESTS && !SKIP_ELGAMAL_TEST) unittest 297 { 298 logDebug("Testing elgamal.d ..."); 299 size_t fails = 0; 300 301 Unique!AutoSeededRNG rng = new AutoSeededRNG; 302 303 fails += testPkKeygen(*rng); 304 305 File elgamal_enc = File("../test_data/pubkey/elgamal.vec", "r"); 306 307 fails += runTestsBb(elgamal_enc, "ElGamal Encryption", "Ciphertext", true, 308 (ref HashMap!(string, string) m) { 309 return elgamalKat(m["P"], m["G"], m["X"], m["Msg"], 310 m.get("Padding", ""), m["Nonce"], m["Ciphertext"]); 311 }); 312 313 testReport("elg", total_tests, fails); 314 }