1 /* $Id: ElGamalCipher.java,v 1.11 2005/03/28 14:26:11 woudt Exp $ 2 * 3 * Copyright (C) 1995-2000 The Cryptix Foundation Limited. 4 * All rights reserved. 5 * 6 * Use, modification, copying and distribution of this software is subject 7 * the terms and conditions of the Cryptix General Licence. You should have 8 * received a copy of the Cryptix General Licence along with this library; 9 * if not, you can download a copy from http://www.cryptix.org/ . 10 */ 11 package cryptix.jce.provider.elgamal; 12 13 import javax.crypto.Cipher; 14 import javax.crypto.CipherSpi; 15 import javax.crypto.KeyGenerator; 16 import javax.crypto.SecretKeyFactory; 17 import javax.crypto.NoSuchPaddingException; 18 import javax.crypto.BadPaddingException; 19 import javax.crypto.ShortBufferException; 20 import javax.crypto.IllegalBlockSizeException; 21 import javax.crypto.spec.SecretKeySpec; 22 23 import java.math.BigInteger; 24 import java.security.Key; 25 import java.security.SecureRandom; 26 import java.security.spec.AlgorithmParameterSpec; 27 import java.security.AlgorithmParameters; 28 import java.security.NoSuchProviderException; 29 import java.security.NoSuchAlgorithmException; 30 import java.security.InvalidKeyException; 31 import java.security.InvalidAlgorithmParameterException; 32 import java.security.spec.InvalidKeySpecException; 33 34 import cryptix.jce.ElGamalKey; 35 import cryptix.jce.ElGamalParams; 36 import cryptix.jce.ElGamalPrivateKey; 37 import cryptix.jce.ElGamalPublicKey; 38 39 import cryptix.jce.provider.util.Util; 40 41 /** 42 * Expirimental ElGamal implementation. 43 * 44 * @author Paul Waserbrot (pw@cryptix.org) 45 * @author Kevin Dana, Agorics Inc. (Agorics mod: 16273) 46 * @version $Revision: 1.11 $ 47 */ 48 public final class ElGamalCipher extends CipherSpi { 49 50 private BigInteger p, g, z; 51 52 53 private int messageMaxLength; 54 55 56 private boolean decrypt; 57 ElGamalCipher()58 public ElGamalCipher() { 59 super(); 60 } 61 62 // FIXME: Add(?) a skipLeadingZero method, since BigInteger add a leading 63 // zero if MSB > 0x7F.. ..hm, that's why i had to add 1 byte in RSACipher 64 // Found it when debugged a test progie for ElGamalAlgorithm. (pw) 65 66 protected final void engineSetMode(String mode)67 engineSetMode(String mode) 68 throws NoSuchAlgorithmException { 69 if (!mode.equalsIgnoreCase("ECB")) 70 throw new NoSuchAlgorithmException("Wrong mode type!"); 71 } 72 73 74 protected final void engineSetPadding(String padding)75 engineSetPadding(String padding) 76 throws NoSuchPaddingException { 77 if (!padding.equalsIgnoreCase("PKCS1") 78 && !padding.equalsIgnoreCase("PKCS#1") 79 && !padding.equalsIgnoreCase("PKCS1Padding")) 80 { 81 // Added as many cases i could think of.. (pw) 82 throw new NoSuchPaddingException("Wrong padding scheme!"); 83 } 84 } 85 86 87 protected final int engineGetBlockSize()88 engineGetBlockSize() { 89 //we should use p.bitLength() rounded up instead of messageMaxLength 90 //which is rounded down. 91 return ((p.bitLength() + 7) / 8) * 2; 92 } 93 94 engineGetKeySize(Key key)95 protected int engineGetKeySize(Key key) throws InvalidKeyException { 96 97 if( !(key instanceof ElGamalKey) ) 98 throw new InvalidKeyException("Not an ElGamalKey!"); 99 100 ElGamalParams params = ((ElGamalKey)key).getParams(); 101 return params.getP().bitLength(); 102 } 103 104 105 protected final int engineGetOutputSize(int inputLen)106 engineGetOutputSize(int inputLen) { 107 return (inputLen < this.engineGetBlockSize()+1) ? 108 this.engineGetBlockSize() + 1: inputLen; 109 } 110 111 112 protected final byte[] engineGetIV()113 engineGetIV() { 114 return null; 115 } 116 117 118 protected final AlgorithmParameters engineGetParameters()119 engineGetParameters() { 120 throw new RuntimeException("NYI"); 121 } 122 123 /* FIXME: Add something about weak keys.. 124 * comment from Jeroen 125 * "and state in the doco that that is considered pretty 126 * weak and then point to www.cryptosavvy.com " (pw) 127 */ 128 protected final void engineInit(int opmode, Key key, SecureRandom random)129 engineInit(int opmode, Key key, SecureRandom random) 130 throws InvalidKeyException { 131 decrypt = opmode == Cipher.DECRYPT_MODE; 132 if(!(key instanceof ElGamalKey) ) 133 { 134 throw new InvalidKeyException("Not an ElGamalKey"); 135 } 136 g = ((ElGamalParams)((ElGamalKey)key).getParams() ).getG(); 137 p = ((ElGamalParams)((ElGamalKey)key).getParams() ).getP(); 138 if(decrypt) 139 { 140 if(!(key instanceof ElGamalPrivateKey) ) 141 { 142 throw new InvalidKeyException("Not a private key"); 143 } 144 z = ((ElGamalPrivateKey)key).getX(); 145 } 146 else 147 { 148 if(!(key instanceof ElGamalPublicKey) ) 149 { 150 throw new InvalidKeyException("Not a public key" ); 151 } 152 z = ((ElGamalPublicKey)key).getY(); 153 } 154 messageMaxLength = (p.bitLength() - 1)/8; 155 } 156 157 158 protected final void engineInit(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random)159 engineInit(int opmode, Key key, AlgorithmParameterSpec params, 160 SecureRandom random) 161 throws InvalidKeyException, InvalidAlgorithmParameterException { 162 throw new InvalidAlgorithmParameterException( 163 "This cipher do not support AlgorithmParameterSpecs"); 164 } 165 166 167 protected final void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random)168 engineInit(int opmode, Key key, AlgorithmParameters params, 169 SecureRandom random) 170 throws InvalidKeyException, InvalidAlgorithmParameterException { 171 throw new InvalidAlgorithmParameterException( 172 "This cipher do not support AlgorithmParameters"); 173 } 174 175 176 protected final byte[] engineUpdate(byte[] input, int inputOffset, int inputLen)177 engineUpdate(byte[] input, int inputOffset, int inputLen) { 178 throw new RuntimeException("You can't do an update when using PKCS1!"); 179 /* Or should we buffer everything until doFinal 180 * or maybe .update() the buffer as many blocksizes a possible and 181 * then buffer (like we do for blockciphers)? 182 * IMO a bad idea! (pw) 183 */ 184 } 185 186 187 protected final int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset)188 engineUpdate(byte[] input, int inputOffset, int inputLen, 189 byte[] output, int outputOffset) 190 throws ShortBufferException { 191 throw new RuntimeException("You can't do an update when using PKCS1!"); 192 /* Or should we buffer everything until doFinal?? 193 * or maybe .update() the buffer as many blocksizes a possible and 194 * then buffer (like we do for blockciphers)? 195 * IMO a bad idea! (pw) 196 */ 197 } 198 199 200 protected final byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)201 engineDoFinal(byte[] input, int inputOffset, int inputLen) 202 throws IllegalBlockSizeException, BadPaddingException { 203 byte [] o = new byte[this.engineGetOutputSize(inputLen)]; 204 int ret; 205 try { 206 ret = this.engineDoFinal(input, inputOffset, inputLen, o, 0); 207 if (ret == o.length) 208 return o; 209 } catch (ShortBufferException e) { 210 throw new RuntimeException("PANIC: Should not happned!"); 211 } 212 213 // If the buffer returned is smaller than what we allocated first. 214 byte [] r = new byte[ret]; 215 System.arraycopy(o, 0, r, 0, ret); 216 return r; 217 } 218 219 220 protected final int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset)221 engineDoFinal(byte[] input, int inputOffset, int inputLen, 222 byte[] output, int outputOffset) 223 throws ShortBufferException, IllegalBlockSizeException, BadPaddingException 224 { 225 // FIXME: isn't it a bug here? We must take length - offset! (pw) 226 if (output.length < this.engineGetOutputSize(inputLen)) 227 throw new ShortBufferException("Output buffer too small!"); 228 229 BigInteger bi; 230 BigInteger [] res; 231 byte [] tmp1, tmp2, b; 232 233 int blocksize = engineGetBlockSize() / 2; 234 if (decrypt) { 235 res = new BigInteger[2]; 236 tmp1 = new byte[blocksize]; 237 System.arraycopy(input, 0, tmp1, 0, blocksize); 238 res[0] = new BigInteger(1, tmp1); 239 tmp2 = new byte[blocksize]; 240 System.arraycopy(input, blocksize, tmp2, 0, blocksize); 241 res[1] = new BigInteger(1, tmp2); 242 243 BigInteger m = null; 244 245 try { 246 m = ElGamalAlgorithm.decrypt(res, p, z); 247 } catch (ArithmeticException e) { 248 // This is not really a bad padding exception, however we are 249 // not allowed to throw other exceptions and this one comes the 250 // closest to what is going on here (applications should handle 251 // them the same way anyway). 252 throw new BadPaddingException("Decryption Failed."); 253 } 254 255 b = Util.toFixedLenByteArray(m, blocksize); 256 return unpad(b, b.length, 0, output, outputOffset); 257 } else { 258 /* FIXME: Do so we choose right block type out of the keytype? 259 * (pw) 260 */ 261 bi = new BigInteger(1, pad(input, inputLen, 262 inputOffset, 0x02)); 263 res = ElGamalAlgorithm.encrypt(bi, p, g, z); 264 265 tmp1 = Util.toFixedLenByteArray(res[0], blocksize); 266 tmp2 = Util.toFixedLenByteArray(res[1], blocksize); 267 268 System.arraycopy(tmp1,0,output,outputOffset, tmp1.length); 269 System.arraycopy(tmp2,0,output,outputOffset + tmp1.length, 270 tmp2.length); 271 272 return tmp1.length + tmp2.length; 273 } 274 } 275 276 277 protected byte[] engineWrap(Key key)278 engineWrap(Key key) 279 throws IllegalBlockSizeException, InvalidKeyException { 280 // FIXME: Should we do some sanity check of the key?? (pw) 281 String format = key.getFormat(); 282 // FIXME: Add so we take more than just keys from blockciphers (pw) 283 if (format == null || !format.equalsIgnoreCase("RAW")) 284 throw new InvalidKeyException("Wrong format on key!"); 285 byte [] buf = key.getEncoded(); 286 try { 287 return this.engineDoFinal(buf, 0, buf.length); 288 } catch (BadPaddingException e) { 289 throw new RuntimeException("PANIC: This should not happend!"); 290 } 291 } 292 293 protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType)294 engineUnwrap(byte[] wrappedKey, 295 String wrappedKeyAlgorithm, 296 int wrappedKeyType) 297 throws InvalidKeyException, NoSuchAlgorithmException { 298 // FIXME: Add so we also support private and publickeys (pw) 299 if (wrappedKeyType != Cipher.SECRET_KEY) 300 throw new InvalidKeyException("Wrong keytype!"); 301 302 303 try { 304 // FIXME: HACK! Do test to see if we support the algorithm 305 // Do we need to do this??? (pw) 306 KeyGenerator.getInstance(wrappedKeyAlgorithm, "Cryptix"); 307 308 byte [] buf = this.engineDoFinal(wrappedKey, 0, wrappedKey.length); 309 310 // FIXME: Shall we check for DES keys and use DESKeySpec? (pw) 311 SecretKeySpec sks = new SecretKeySpec(buf, 0, buf.length, 312 wrappedKeyAlgorithm); 313 314 SecretKeyFactory skf = 315 SecretKeyFactory.getInstance(wrappedKeyAlgorithm); 316 return skf.generateSecret(sks); 317 318 } catch (NoSuchAlgorithmException e) { // Gee i'm so polite (pw) 319 throw new NoSuchAlgorithmException("Algorithm not supported!"); 320 } catch (NoSuchProviderException e) { 321 throw new RuntimeException("PANIC: Should not happend!"); 322 } catch (BadPaddingException e) { 323 throw new RuntimeException("PANIC: This should not happend!"); 324 } catch (IllegalBlockSizeException e) { 325 throw new RuntimeException("PANIC: This should not happend!"); 326 } catch (InvalidKeySpecException e) { 327 throw new RuntimeException("PANIC: This should not happend!"); 328 } 329 } 330 331 332 /* 333 * Should we implement this one?? (pw) 334 protected int 335 engineGetKeySize(Key key) 336 throws InvalidKeyException { 337 throw new UnsupportedOperationException( 338 "Not implemented by the provider!"); 339 } 340 */ 341 342 343 // Private methods 344 //............................................................................ 345 346 /* 347 * This is PKCS1 padding as described in the PKCS1 v 1.5 348 * standard section 8 from RSALabs: 349 * EB = 00 || BT || PS || 00 || D. 350 * 351 * But since BigInteger actually removes any leading zero 352 * the encrypted buffer will be without the first 00. 353 * 354 * Both pad and unpad assumes us to have check so that the 355 * output buffer is of valid size. 356 * 357 * I have done so we may use both private and public keys 358 * as input, ie BT may be either 0x00, 0x01 or 0x02. (pw) 359 */ pad(byte[] input, int inputLen, int offset, int bt)360 private byte[] pad(byte[] input, int inputLen, int offset, int bt) 361 throws BadPaddingException { 362 int k = (p.bitLength() + 7)/8; 363 if (inputLen > k-11) 364 throw new BadPaddingException("Data too long for this modulus!"); 365 366 byte[] ed = new byte[k]; 367 int padLen = k - 3 - inputLen; 368 ed[0] = ed[2 + padLen] = 0x00; 369 370 switch (bt) { 371 case 0x00: 372 for (int i = 1; i < (2 + padLen); i++) 373 ed[i] = 0x00; 374 break; 375 case 0x01: 376 ed[1] = 0x01; 377 for (int i = 2; i < (2 + padLen); i++) 378 ed[i] = (byte)0xFF; 379 break; 380 case 0x02: 381 ed[1] = 0x02; 382 byte [] b = new byte[1]; 383 SecureRandom sr = new SecureRandom(); 384 for (int i = 2; i < (2 + padLen); i++) { 385 b[0] = 0; 386 while (b[0] == 0) 387 sr.nextBytes(b); 388 ed[i] = b[0]; 389 } 390 break; 391 default: 392 throw new BadPaddingException("Wrong block type!"); 393 } 394 395 System.arraycopy(input, offset, ed, padLen + 3, inputLen); 396 return ed; 397 } 398 399 unpad(byte[] input, int inputLen, int inOffset, byte[] output, int outOffset)400 private int unpad(byte[] input, int inputLen, int inOffset, 401 byte[] output, int outOffset) 402 throws BadPaddingException { 403 int bt = input[inOffset + 1]; 404 int padLen = 1; 405 try { 406 switch (bt) { 407 case 0x00: 408 for (;; padLen++) 409 if (input[inOffset + padLen + 1] != (byte)0x00) break; 410 break; 411 case 0x01: 412 case 0x02: 413 for (;; padLen++) 414 if (input[inOffset + padLen] == (byte)0x00) break; 415 break; 416 default: 417 throw new BadPaddingException("Wrong block type!"); 418 } 419 } catch (ArrayIndexOutOfBoundsException ex) { 420 throw new BadPaddingException( 421 "Cannot unpad: padding incorrect for PKCS#1 block type "+bt); 422 } 423 424 padLen++; 425 426 int len = inputLen - inOffset - padLen; 427 System.arraycopy(input, inOffset + padLen, output, outOffset, len); 428 return len; 429 } 430 } 431