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 m_key = key; 150 assert(key.algoName == ElGamalPublicKey.algoName); 151 const BigInt* p = &m_key.groupP(); 152 153 m_powermod_g_p = FixedBasePowerMod(&m_key.groupG(), p); 154 m_powermod_y_p = FixedBasePowerMod(&m_key.getY(), p); 155 m_mod_p = ModularReducer(*p); 156 } 157 158 override SecureVector!ubyte encrypt(const(ubyte)* msg, size_t msg_len, RandomNumberGenerator rng) 159 { 160 const BigInt* p = &m_mod_p.getModulus(); 161 162 BigInt m = BigInt(msg, msg_len); 163 164 if (m >= *p) 165 throw new InvalidArgument("ElGamal encryption: Input is too large"); 166 167 BigInt k = BigInt(rng, 2 * dlWorkFactor(p.bits())); 168 BigInt g_p = (cast()*m_powermod_g_p)(&k); 169 BigInt y_p_0 = (cast()*m_powermod_y_p)(&k); 170 BigInt y_p = m_mod_p.multiply(&m, &y_p_0); 171 172 SecureVector!ubyte output = SecureVector!ubyte(2*p.bytes()); 173 g_p.binaryEncode(&output[p.bytes() - g_p.bytes()]); 174 y_p.binaryEncode(&output[output.length / 2 + (p.bytes() - y_p.bytes())]); 175 return output; 176 } 177 178 private: 179 const DLSchemePublicKey m_key; 180 FixedBasePowerMod m_powermod_g_p, m_powermod_y_p; 181 ModularReducer m_mod_p; 182 } 183 184 /** 185 * ElGamal decryption operation 186 */ 187 final class ElGamalDecryptionOperation : Decryption 188 { 189 public: 190 override size_t maxInputBits() const { return m_mod_p.getModulus().bits() - 1; } 191 192 this(in PrivateKey pkey, RandomNumberGenerator rng) { 193 this(cast(DLSchemePrivateKey) pkey, rng); 194 } 195 196 this(in ElGamalPrivateKey key, RandomNumberGenerator rng) 197 { 198 this(key.m_priv, rng); 199 } 200 201 this(in DLSchemePrivateKey key, RandomNumberGenerator rng) 202 { 203 assert(key.algoName == ElGamalPublicKey.algoName); 204 const BigInt* p = &key.groupP(); 205 206 m_powermod_x_p = FixedExponentPowerMod(&key.getX(), p); 207 m_mod_p = ModularReducer(*p); 208 209 BigInt k = BigInt(rng, p.bits() - 1); 210 auto d = (cast()*m_powermod_x_p)(&k); 211 m_blinder = Blinder(k, d, *p); 212 } 213 214 override SecureVector!ubyte decrypt(const(ubyte)* msg, size_t msg_len) 215 { 216 const BigInt* p = &m_mod_p.getModulus(); 217 218 const size_t p_bytes = p.bytes(); 219 220 if (msg_len != 2 * p_bytes) 221 throw new InvalidArgument("ElGamal decryption: Invalid message"); 222 223 BigInt a = BigInt(msg, p_bytes); 224 BigInt b = BigInt(msg + p_bytes, p_bytes); 225 226 if (a >= *p || b >= *p) 227 throw new InvalidArgument("ElGamal decryption: Invalid message"); 228 229 a = m_blinder.blind(a); 230 auto r_0 = (cast()*m_powermod_x_p)(&a); 231 auto inv_res = inverseMod(&r_0, p); 232 BigInt r = m_mod_p.multiply(&b, &inv_res); 233 234 return BigInt.encodeLocked(m_blinder.unblind(r)); 235 } 236 private: 237 FixedExponentPowerMod m_powermod_x_p; 238 ModularReducer m_mod_p; 239 Blinder m_blinder; 240 } 241 242 static if (BOTAN_TEST): 243 import botan.test; 244 import botan.pubkey.test; 245 import botan.pubkey.pubkey; 246 import botan.codec.hex; 247 import botan.pubkey.algo.dl_group; 248 import botan.rng.auto_rng; 249 import core.atomic; 250 import memutils.hashmap; 251 252 private shared size_t total_tests; 253 254 size_t testPkKeygen(RandomNumberGenerator rng) 255 { 256 size_t fails; 257 258 string[] elg_list = ["modp/ietf/1024", "dsa/jce/1024", "dsa/botan/2048", "dsa/botan/3072"]; 259 260 foreach (elg; elg_list) { 261 atomicOp!"+="(total_tests, 1); 262 auto key = ElGamalPrivateKey(rng, DLGroup(elg)); 263 key.checkKey(rng, true); 264 fails += validateSaveAndLoad(key, rng); 265 } 266 267 return fails; 268 } 269 270 size_t elgamalKat(string p, 271 string g, 272 string x, 273 string msg, 274 string padding, 275 string nonce, 276 string ciphertext) 277 { 278 atomicOp!"+="(total_tests, 1); 279 Unique!AutoSeededRNG rng = new AutoSeededRNG; 280 281 BigInt p_bn = BigInt(p); 282 BigInt g_bn = BigInt(g); 283 BigInt x_bn = BigInt(x); 284 285 DLGroup group = DLGroup(p_bn, g_bn); 286 auto privkey = ElGamalPrivateKey(*rng, group.move(), x_bn.move()); 287 288 auto pubkey = ElGamalPublicKey(privkey); 289 290 if (padding == "") 291 padding = "Raw"; 292 293 auto enc = scoped!PKEncryptorEME(pubkey, padding); 294 auto dec = scoped!PKDecryptorEME(privkey, padding); 295 296 return validateEncryption(enc, dec, "ElGamal/" ~ padding, msg, nonce, ciphertext); 297 } 298 299 static if (BOTAN_HAS_TESTS && !SKIP_ELGAMAL_TEST) unittest 300 { 301 logDebug("Testing elgamal.d ..."); 302 size_t fails = 0; 303 304 Unique!AutoSeededRNG rng = new AutoSeededRNG; 305 306 fails += testPkKeygen(*rng); 307 308 File elgamal_enc = File("test_data/pubkey/elgamal.vec", "r"); 309 310 fails += runTestsBb(elgamal_enc, "ElGamal Encryption", "Ciphertext", true, 311 (ref HashMap!(string, string) m) { 312 return elgamalKat(m["P"], m["G"], m["X"], m["Msg"], 313 m.get("Padding", ""), m["Nonce"], m["Ciphertext"]); 314 }); 315 316 testReport("elg", total_tests, fails); 317 }