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