Source for gnu.javax.crypto.keyring.PasswordEncryptedEntry

   1: /* PasswordEncryptedEntry.java --
   2:    Copyright (C) 2003, 2006 Free Software Foundation, Inc.
   3: 
   4: This file is a part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2 of the License, or (at
   9: your option) any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; if not, write to the Free Software
  18: Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
  19: USA
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version.  */
  37: 
  38: 
  39: package gnu.javax.crypto.keyring;
  40: 
  41: import gnu.java.security.Configuration;
  42: import gnu.java.security.Registry;
  43: import gnu.java.security.prng.IRandom;
  44: import gnu.java.security.prng.LimitReachedException;
  45: import gnu.java.security.util.PRNG;
  46: import gnu.java.security.util.Util;
  47: import gnu.javax.crypto.cipher.CipherFactory;
  48: import gnu.javax.crypto.cipher.IBlockCipher;
  49: import gnu.javax.crypto.mode.IMode;
  50: import gnu.javax.crypto.mode.ModeFactory;
  51: import gnu.javax.crypto.pad.IPad;
  52: import gnu.javax.crypto.pad.PadFactory;
  53: import gnu.javax.crypto.pad.WrongPaddingException;
  54: import gnu.javax.crypto.prng.IPBE;
  55: import gnu.javax.crypto.prng.PRNGFactory;
  56: 
  57: import java.io.ByteArrayInputStream;
  58: import java.io.ByteArrayOutputStream;
  59: import java.io.DataInputStream;
  60: import java.io.DataOutputStream;
  61: import java.io.IOException;
  62: import java.security.InvalidKeyException;
  63: import java.util.HashMap;
  64: import java.util.Iterator;
  65: import java.util.logging.Logger;
  66: 
  67: /**
  68:  * An envelope that is encrypted with a password-derived key.
  69:  */
  70: public class PasswordEncryptedEntry
  71:     extends MaskableEnvelopeEntry
  72:     implements PasswordProtectedEntry, Registry
  73: {
  74:   private static final Logger log = Logger.getLogger(PasswordEncryptedEntry.class.getName());
  75:   public static final int TYPE = 1;
  76: 
  77:   public PasswordEncryptedEntry(String cipher, String mode, int keylen,
  78:                                 Properties properties)
  79:   {
  80:     super(TYPE, properties);
  81:     if ((cipher == null || cipher.length() == 0)
  82:         || (mode == null || mode.length() == 0))
  83:       throw new IllegalArgumentException("cipher nor mode can be empty");
  84:     this.properties.put("cipher", cipher);
  85:     this.properties.put("mode", mode);
  86:     this.properties.put("keylen", String.valueOf(keylen));
  87:     setMasked(false);
  88:   }
  89: 
  90:   private PasswordEncryptedEntry()
  91:   {
  92:     super(TYPE);
  93:     setMasked(true);
  94:   }
  95: 
  96:   public static PasswordEncryptedEntry decode(DataInputStream in,
  97:                                               char[] password)
  98:       throws IOException
  99:   {
 100:     PasswordEncryptedEntry entry = decode(in);
 101:     try
 102:       {
 103:         entry.decrypt(password);
 104:       }
 105:     catch (WrongPaddingException wpe)
 106:       {
 107:         throw new MalformedKeyringException("wrong padding in decrypted data");
 108:       }
 109:     return entry;
 110:   }
 111: 
 112:   public static PasswordEncryptedEntry decode(DataInputStream in)
 113:       throws IOException
 114:   {
 115:     PasswordEncryptedEntry entry = new PasswordEncryptedEntry();
 116:     entry.defaultDecode(in);
 117:     return entry;
 118:   }
 119: 
 120:   public void decrypt(char[] password) throws IllegalArgumentException,
 121:       WrongPaddingException
 122:   {
 123:     if (Configuration.DEBUG)
 124:       log.entering(this.getClass().getName(), "decrypt");
 125:     if (isMasked() && payload != null)
 126:       {
 127:         long tt = -System.currentTimeMillis();
 128:         IMode mode = getMode(password, IMode.DECRYPTION);
 129:         IPad padding = PadFactory.getInstance("PKCS7");
 130:         padding.init(mode.currentBlockSize());
 131:         byte[] buf = new byte[payload.length];
 132:         int count = 0;
 133:         while (count + mode.currentBlockSize() <= payload.length)
 134:           {
 135:             mode.update(payload, count, buf, count);
 136:             count += mode.currentBlockSize();
 137:           }
 138:         int padlen = padding.unpad(buf, 0, buf.length);
 139:         setMasked(false);
 140:         int len = buf.length - padlen;
 141:         ByteArrayInputStream baos = new ByteArrayInputStream(buf, 0, len);
 142:         DataInputStream in = new DataInputStream(baos);
 143:         try
 144:           {
 145:             decodeEnvelope(in);
 146:           }
 147:         catch (IOException ioe)
 148:           {
 149:             throw new IllegalArgumentException("decryption failed");
 150:           }
 151:         tt += System.currentTimeMillis();
 152:         log.fine("Decrypted in " + tt + "ms.");
 153:       }
 154:     else if (Configuration.DEBUG)
 155:       log.fine("Skip decryption; " + (isMasked() ? "null payload" : "unmasked"));
 156:     if (Configuration.DEBUG)
 157:       log.exiting(this.getClass().getName(), "decrypt");
 158:   }
 159: 
 160:   public void encrypt(char[] password) throws IOException
 161:   {
 162:     if (Configuration.DEBUG)
 163:       log.entering(this.getClass().getName(), "encrypt", String.valueOf(password));
 164:     long tt = -System.currentTimeMillis();
 165:     long t1 = -System.currentTimeMillis();
 166:     byte[] salt = new byte[8];
 167:     PRNG.getInstance().nextBytes(salt);
 168:     t1 += System.currentTimeMillis();
 169:     if (Configuration.DEBUG)
 170:       log.fine("-- Generated salt in " + t1 + "ms.");
 171:     properties.put("salt", Util.toString(salt));
 172:     IMode mode = getMode(password, IMode.ENCRYPTION);
 173:     IPad pad = PadFactory.getInstance("PKCS7");
 174:     pad.init(mode.currentBlockSize());
 175:     ByteArrayOutputStream bout = new ByteArrayOutputStream(1024);
 176:     DataOutputStream out2 = new DataOutputStream(bout);
 177:     for (Iterator it = entries.iterator(); it.hasNext();)
 178:       {
 179:         Entry entry = (Entry) it.next();
 180:         if (Configuration.DEBUG)
 181:           log.fine("-- About to encode one " + entry);
 182:         t1 = -System.currentTimeMillis();
 183:         entry.encode(out2);
 184:         t1 += System.currentTimeMillis();
 185:         if (Configuration.DEBUG)
 186:           log.fine("-- Encoded an Entry in " + t1 + "ms.");
 187:       }
 188:     byte[] plaintext = bout.toByteArray();
 189:     byte[] padding = pad.pad(plaintext, 0, plaintext.length);
 190:     payload = new byte[plaintext.length + padding.length];
 191:     byte[] lastBlock = new byte[mode.currentBlockSize()];
 192:     int l = mode.currentBlockSize() - padding.length;
 193:     System.arraycopy(plaintext, plaintext.length - l, lastBlock, 0, l);
 194:     System.arraycopy(padding, 0, lastBlock, l, padding.length);
 195:     int count = 0;
 196:     while (count + mode.currentBlockSize() < plaintext.length)
 197:       {
 198:         mode.update(plaintext, count, payload, count);
 199:         count += mode.currentBlockSize();
 200:       }
 201:     mode.update(lastBlock, 0, payload, count);
 202:     setMasked(true);
 203:     tt += System.currentTimeMillis();
 204:     if (Configuration.DEBUG)
 205:       {
 206:         log.fine("Encrypted in " + tt + "ms.");
 207:         log.exiting(this.getClass().getName(), "encrypt");
 208:       }
 209:   }
 210: 
 211:   public void encode(DataOutputStream out, char[] password) throws IOException
 212:   {
 213:     encrypt(password);
 214:     encode(out);
 215:   }
 216: 
 217:   protected void encodePayload() throws IOException
 218:   {
 219:     if (payload == null)
 220:       {
 221:         if (Configuration.DEBUG)
 222:           log.fine("Null payload: " + this);
 223:         throw new IllegalStateException("not encrypted");
 224:       }
 225:   }
 226: 
 227:   private IMode getMode(char[] password, int state)
 228:   {
 229:     String s = properties.get("salt");
 230:     if (s == null)
 231:       throw new IllegalArgumentException("no salt");
 232:     byte[] salt = Util.toBytesFromString(s);
 233:     IBlockCipher cipher = CipherFactory.getInstance(properties.get("cipher"));
 234:     if (cipher == null)
 235:       throw new IllegalArgumentException("no such cipher: "
 236:                                          + properties.get("cipher"));
 237:     int blockSize = cipher.defaultBlockSize();
 238:     if (properties.containsKey("block-size"))
 239:       try
 240:         {
 241:           blockSize = Integer.parseInt(properties.get("block-size"));
 242:         }
 243:       catch (NumberFormatException nfe)
 244:         {
 245:           throw new IllegalArgumentException("bad block size: "
 246:                                              + nfe.getMessage());
 247:         }
 248:     String modeName = properties.get("mode");
 249:     IMode mode = ModeFactory.getInstance(modeName, cipher, blockSize);
 250:     if (mode == null)
 251:       throw new IllegalArgumentException("no such mode: " + modeName);
 252:     HashMap pbAttr = new HashMap();
 253:     pbAttr.put(IPBE.PASSWORD, password);
 254:     pbAttr.put(IPBE.SALT, salt);
 255:     pbAttr.put(IPBE.ITERATION_COUNT, ITERATION_COUNT);
 256:     IRandom kdf = PRNGFactory.getInstance("PBKDF2-HMAC-SHA");
 257:     kdf.init(pbAttr);
 258:     int keylen = 0;
 259:     if (! properties.containsKey("keylen"))
 260:       throw new IllegalArgumentException("no key length");
 261:     try
 262:       {
 263:         keylen = Integer.parseInt(properties.get("keylen"));
 264:       }
 265:     catch (NumberFormatException nfe)
 266:       {
 267:       }
 268:     byte[] dk = new byte[keylen];
 269:     byte[] iv = new byte[blockSize];
 270:     try
 271:       {
 272:         kdf.nextBytes(dk, 0, keylen);
 273:         kdf.nextBytes(iv, 0, blockSize);
 274:       }
 275:     catch (LimitReachedException shouldNotHappen)
 276:       {
 277:         throw new Error(shouldNotHappen.toString());
 278:       }
 279:     HashMap modeAttr = new HashMap();
 280:     modeAttr.put(IMode.KEY_MATERIAL, dk);
 281:     modeAttr.put(IMode.STATE, Integer.valueOf(state));
 282:     modeAttr.put(IMode.IV, iv);
 283:     try
 284:       {
 285:         mode.init(modeAttr);
 286:       }
 287:     catch (InvalidKeyException ike)
 288:       {
 289:         throw new IllegalArgumentException(ike.toString());
 290:       }
 291:     return mode;
 292:   }
 293: }