/* Cipher.java -- Interface to a cryptographic cipher. Copyright (C) 2004 Free Software Foundation, Inc. This file is part of GNU Classpath. GNU Classpath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. GNU Classpath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Classpath; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. Linking this library statically or dynamically with other modules is making a combined work based on this library. Thus, the terms and conditions of the GNU General Public License cover the whole combination. As a special exception, the copyright holders of this library give you permission to link this library with independent modules to produce an executable, regardless of the license terms of these independent modules, and to copy and distribute the resulting executable under terms of your choice, provided that you also meet, for each linked independent module, the terms and conditions of the license of that module. An independent module is a module which is not derived from or based on this library. If you modify this library, you may extend this exception to your version of the library, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ package javax.crypto; import gnu.java.security.Engine; import java.security.AlgorithmParameters; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Provider; import java.security.SecureRandom; import java.security.Security; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.security.spec.AlgorithmParameterSpec; import java.util.StringTokenizer; /** *
This class implements a cryptographic cipher for transforming * data.
* *Ciphers cannot be instantiated directly; rather one of the
* getInstance
must be used to instantiate a given
* transformation, optionally with a specific provider.
A transformation is of the form:
* *where algorithm is the base name of a cryptographic cipher * (such as "AES"), mode is the abbreviated name of a block * cipher mode (such as "CBC" for cipher block chaining mode), and * padding is the name of a padding scheme (such as * "PKCS5Padding"). If only the algorithm name is supplied, then the * provider-specific default mode and padding will be used.
* *An example transformation is:
* *Cipher c =
* Cipher.getInstance("AES/CBC/PKCS5Padding");
*
* Finally, when requesting a block cipher in stream cipher mode
* (such as AES
* in OFB or CFB mode) the number of bits to be processed
* at a time may be specified by appending it to the name of the mode;
* e.g. "AES/OFB8/NoPadding"
. If no such number is
* specified a provider-specific default value is used.
init
methods have not been called, and therefore no
* transformations can be done.
*/
private static final int INITIAL_STATE = 0;
/** The underlying cipher service provider interface. */
private CipherSpi cipherSpi;
/** The provider from which this instance came. */
private Provider provider;
/** The transformation requested. */
private String transformation;
/** Our current state (encrypting, wrapping, etc.) */
private int state;
// Class methods.
// ------------------------------------------------------------------------
/**
* Creates a new cipher instance for the given transformation.
* *The installed providers are tried in order for an * implementation, and the first appropriate instance is returned. If * no installed provider can provide the implementation, an * appropriate exception is thrown.
* * @param transformation The transformation to create. * @return An appropriate cipher for this transformation. * @throws java.security.NoSuchAlgorithmException If no installed * provider can supply the appropriate cipher or mode. * @throws javax.crypto.NoSuchPaddingException If no installed * provider can supply the appropriate padding. */ public static final Cipher getInstance(String transformation) throws NoSuchAlgorithmException, NoSuchPaddingException { Provider[] providers = Security.getProviders(); NoSuchPaddingException ex = null; String msg = ""; for (int i = 0; i < providers.length; i++) { try { return getInstance(transformation, providers[i]); } catch (NoSuchAlgorithmException nsae) { msg = nsae.getMessage(); ex = null; } catch (NoSuchPaddingException nspe) { ex = nspe; } } if (ex != null) { throw ex; } throw new NoSuchAlgorithmException(msg); } /** *Creates a new cipher instance for the given transformation and * the named provider.
* * @param transformation The transformation to create. * @param provider The name of the provider to use. * @return An appropriate cipher for this transformation. * @throws java.security.NoSuchAlgorithmException If the provider cannot * supply the appropriate cipher or mode. * @throws java.security.NoSuchProviderException If the named provider * is not installed. * @throws javax.crypto.NoSuchPaddingException If the provider cannot * supply the appropriate padding. */ public static final Cipher getInstance(String transformation, String provider) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException { Provider p = Security.getProvider(provider); if (p == null) { throw new NoSuchProviderException(provider); } return getInstance(transformation, p); } /** * Creates a new cipher instance for the given transform and the given * provider. * * @param transformation The transformation to create. * @param provider The provider to use. * @return An appropriate cipher for this transformation. * @throws java.security.NoSuchAlgorithmException If the given * provider cannot supply the appropriate cipher or mode. * @throws javax.crypto.NoSuchPaddingException If the given * provider cannot supply the appropriate padding scheme. */ public static final Cipher getInstance(String transformation, Provider provider) throws NoSuchAlgorithmException, NoSuchPaddingException { CipherSpi result = null; String key = null; String alg = null, mode = null, pad = null; String msg = ""; if (transformation.indexOf('/') < 0) { try { result = (CipherSpi) Engine.getInstance(SERVICE, transformation, provider); return new Cipher(result, provider, transformation); } catch (Exception e) { msg = e.getMessage(); } } else { StringTokenizer tok = new StringTokenizer(transformation, "/"); if (tok.countTokens() != 3) { throw new NoSuchAlgorithmException("badly formed transformation"); } alg = tok.nextToken(); mode = tok.nextToken(); pad = tok.nextToken(); try { result = (CipherSpi) Engine.getInstance(SERVICE, transformation, provider); return new Cipher(result, provider, transformation); } catch (Exception e) { msg = e.getMessage(); } try { result = (CipherSpi) Engine.getInstance(SERVICE, alg + '/' + mode, provider); result.engineSetPadding(pad); return new Cipher(result, provider, transformation); } catch (Exception e) { if (e instanceof NoSuchPaddingException) { throw (NoSuchPaddingException) e; } msg = e.getMessage(); } try { result = (CipherSpi) Engine.getInstance(SERVICE, alg + "//" + pad, provider); result.engineSetMode(mode); return new Cipher(result, provider, transformation); } catch (Exception e) { msg = e.getMessage(); } try { result = (CipherSpi) Engine.getInstance(SERVICE, alg, provider); result.engineSetMode(mode); result.engineSetPadding(pad); return new Cipher(result, provider, transformation); } catch (Exception e) { if (e instanceof NoSuchPaddingException) { throw (NoSuchPaddingException) e; } msg = e.getMessage(); } } throw new NoSuchAlgorithmException(transformation + ": " + msg); } // Constructor. // ------------------------------------------------------------------------ /** * Create a cipher. * * @param cipherSpi The underlying implementation of the cipher. * @param provider The provider of this cipher implementation. * @param transformation The transformation this cipher performs. */ protected Cipher(CipherSpi cipherSpi, Provider provider, String transformation) { this.cipherSpi = cipherSpi; this.provider = provider; this.transformation = transformation; state = INITIAL_STATE; } // Public instance methods. // ------------------------------------------------------------------------ /** * Get the name that this cipher instance was created with; this is * equivalent to the "transformation" argument given to any of the * {@link #getInstance()} methods. * * @return The cipher name. */ public final String getAlgorithm() { return transformation; } /** * Return the size of blocks, in bytes, that this cipher processes. * * @return The block size. */ public final int getBlockSize() { if (cipherSpi != null) { return cipherSpi.engineGetBlockSize(); } return 1; } /** * Return the currently-operating {@link ExemptionMechanism}. * * @return null, currently. */ public final ExemptionMechanism getExemptionMechanism() { return null; } /** * Return the initialization vector that this instance was * initialized with. * * @return The IV. */ public final byte[] getIV() { if (cipherSpi != null) { return cipherSpi.engineGetIV(); } return null; } /** * Return the {@link java.security.AlgorithmParameters} that this * instance was initialized with. * * @return The parameters. */ public final AlgorithmParameters getParameters() { if (cipherSpi != null) { return cipherSpi.engineGetParameters(); } return null; } /** * Return this cipher's provider. * * @return The provider. */ public final Provider getProvider() { return provider; } /** * Finishes a multi-part transformation, and returns the final * transformed bytes. * * @return The final transformed bytes. * @throws java.lang.IllegalStateException If this instance has not * been initialized, or if a doFinal call has already * been made. * @throws javax.crypto.IllegalBlockSizeException If this instance has * no padding and the input is not a multiple of this cipher's * block size. * @throws javax.crypto.BadPaddingException If this instance is * decrypting and the padding bytes do not match this * instance's padding scheme. */ public final byte[] doFinal() throws IllegalStateException, IllegalBlockSizeException, BadPaddingException { return doFinal(new byte[0], 0, 0); } /** * Finishes a multi-part transformation or does an entire * transformation on the input, and returns the transformed bytes. * * @param input The final input bytes. * @return The final transformed bytes. * @throws java.lang.IllegalStateException If this instance has not * been initialized, or if a doFinal call has already * been made. * @throws javax.crypto.IllegalBlockSizeException If this instance has * no padding and the input is not a multiple of this cipher's * block size. * @throws javax.crypto.BadPaddingException If this instance is * decrypting and the padding bytes do not match this * instance's padding scheme. */ public final byte[] doFinal(byte[] input) throws IllegalStateException, IllegalBlockSizeException, BadPaddingException { return doFinal(input, 0, input.length); } /** * Finishes a multi-part transformation or does an entire * transformation on the input, and returns the transformed bytes. * * @param input The final input bytes. * @param inputOffset The index in the input bytes to start. * @param inputLength The number of bytes to read from the input. * @return The final transformed bytes. * @throws java.lang.IllegalStateException If this instance has not * been initialized, or if a doFinal call has already * been made. * @throws javax.crypto.IllegalBlockSizeException If this instance has * no padding and the input is not a multiple of this cipher's * block size. * @throws javax.crypto.BadPaddingException If this instance is * decrypting and the padding bytes do not match this * instance's padding scheme. */ public final byte[] doFinal(byte[] input, int inputOffset, int inputLength) throws IllegalStateException, IllegalBlockSizeException, BadPaddingException { if (cipherSpi == null) { byte[] b = new byte[inputLength]; System.arraycopy(input, inputOffset, b, 0, inputLength); return b; } if (state != ENCRYPT_MODE && state != DECRYPT_MODE) { throw new IllegalStateException("neither encrypting nor decrypting"); } state = INITIAL_STATE; return cipherSpi.engineDoFinal(input, inputOffset, inputLength); } /** * Finishes a multi-part transformation and stores the transformed * bytes into the given array. * * @param output The destination for the transformed bytes. * @param outputOffset The offset in output to start storing * bytes. * @return The number of bytes placed into the output array. * @throws java.lang.IllegalStateException If this instance has not * been initialized, or if a doFinal call has already * been made. * @throws javax.crypto.IllegalBlockSizeException If this instance has * no padding and the input is not a multiple of this cipher's * block size. * @throws javax.crypto.BadPaddingException If this instance is * decrypting and the padding bytes do not match this * instance's padding scheme. * @throws javax.crypto.ShortBufferException If the output array is * not large enough to hold the transformed bytes. */ public final int doFinal(byte[] output, int outputOffset) throws IllegalStateException, IllegalBlockSizeException, BadPaddingException, ShortBufferException { if (cipherSpi == null) { return 0; } if (state != ENCRYPT_MODE && state != DECRYPT_MODE) { throw new IllegalStateException("neither encrypting nor decrypting"); } state = INITIAL_STATE; return cipherSpi.engineDoFinal(new byte[0], 0, 0, output, outputOffset); } /** * Finishes a multi-part transformation or transforms a portion of a * byte array, and stores the result in the given byte array. * * @param input The input bytes. * @param inputOffset The index in input to start. * @param inputLength The number of bytes to transform. * @param output The output buffer. * @param outputOffset The index in output to start. * @return The number of bytes placed into the output array. * @throws java.lang.IllegalStateException If this instance has not * been initialized, or if a doFinal call has already * been made. * @throws javax.crypto.IllegalBlockSizeException If this instance has * no padding and the input is not a multiple of this cipher's * block size. * @throws javax.crypto.BadPaddingException If this instance is * decrypting and the padding bytes do not match this * instance's padding scheme. * @throws javax.crypto.ShortBufferException If the output array is * not large enough to hold the transformed bytes. */ public final int doFinal(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset) throws IllegalStateException, IllegalBlockSizeException, BadPaddingException, ShortBufferException { if (cipherSpi == null) { if (inputLength > output.length - outputOffset) { throw new ShortBufferException(); } System.arraycopy(input, inputOffset, output, outputOffset, inputLength); return inputLength; } if (state != ENCRYPT_MODE && state != DECRYPT_MODE) { throw new IllegalStateException("neither encrypting nor decrypting"); } state = INITIAL_STATE; return cipherSpi.engineDoFinal(input, inputOffset, inputLength, output, outputOffset); } public final int doFinal(byte[] input, int inputOffset, int inputLength, byte[] output) throws IllegalStateException, IllegalBlockSizeException, BadPaddingException, ShortBufferException { return doFinal(input, inputOffset, inputLength, output, 0); } /** * Returns the size an output buffer needs to be if this cipher is * updated with a number of bytes. * * @param inputLength The input length. * @return The output length given this input length. * @throws java.lang.IllegalStateException If this instance has not * been initialized, or if a doFinal call has already * been made. */ public final int getOutputSize(int inputLength) throws IllegalStateException { if (cipherSpi == null) { return inputLength; } if (state != ENCRYPT_MODE && state != DECRYPT_MODE) { throw new IllegalStateException("neither encrypting nor decrypting"); } return cipherSpi.engineGetOutputSize(inputLength); } /** *Initialize this cipher with the public key from the given * certificate.
* *The cipher will be initialized for encryption, decryption, key
* wrapping, or key unwrapping, depending upon whether the
* opmode
argument is {@link #ENCRYPT_MODE}, {@link
* #DECRYPT_MODE}, {@link #WRAP_MODE}, or {@link #UNWRAP_MODE},
* respectively.
As per the Java 1.4 specification, if cert
is an
* instance of an {@link java.security.cert.X509Certificate} and its
* key usage extension field is incompatible with
* opmode
then an {@link
* java.security.InvalidKeyException} is thrown.
If this cipher requires any random bytes (for example for an * initilization vector) than the {@link java.security.SecureRandom} * with the highest priority is used as the source of these bytes.
* *A call to any of the init
methods overrides the
* state of the instance, and is equivalent to creating a new instance
* and calling its init
method.
Initialize this cipher with the supplied key.
* *The cipher will be initialized for encryption, decryption, key
* wrapping, or key unwrapping, depending upon whether the
* opmode
argument is {@link #ENCRYPT_MODE}, {@link
* #DECRYPT_MODE}, {@link #WRAP_MODE}, or {@link #UNWRAP_MODE},
* respectively.
If this cipher requires any random bytes (for example for an * initilization vector) than the {@link java.security.SecureRandom} * with the highest priority is used as the source of these bytes.
* *A call to any of the init
methods overrides the
* state of the instance, and is equivalent to creating a new instance
* and calling its init
method.
Initialize this cipher with the public key from the given * certificate and the specified source of randomness.
* *The cipher will be initialized for encryption, decryption, key
* wrapping, or key unwrapping, depending upon whether the
* opmode
argument is {@link #ENCRYPT_MODE}, {@link
* #DECRYPT_MODE}, {@link #WRAP_MODE}, or {@link #UNWRAP_MODE},
* respectively.
As per the Java 1.4 specification, if cert
is an
* instance of an {@link java.security.cert.X509Certificate} and its
* key usage extension field is incompatible with
* opmode
then an {@link
* java.security.InvalidKeyException} is thrown.
If this cipher requires any random bytes (for example for an * initilization vector) than the {@link java.security.SecureRandom} * with the highest priority is used as the source of these bytes.
* *A call to any of the init
methods overrides the
* state of the instance, and is equivalent to creating a new instance
* and calling its init
method.
Initialize this cipher with the supplied key and source of * randomness.
* *The cipher will be initialized for encryption, decryption, key
* wrapping, or key unwrapping, depending upon whether the
* opmode
argument is {@link #ENCRYPT_MODE}, {@link
* #DECRYPT_MODE}, {@link #WRAP_MODE}, or {@link #UNWRAP_MODE},
* respectively.
A call to any of the init
methods overrides the
* state of the instance, and is equivalent to creating a new instance
* and calling its init
method.
Initialize this cipher with the supplied key and parameters.
* *The cipher will be initialized for encryption, decryption, key
* wrapping, or key unwrapping, depending upon whether the
* opmode
argument is {@link #ENCRYPT_MODE}, {@link
* #DECRYPT_MODE}, {@link #WRAP_MODE}, or {@link #UNWRAP_MODE},
* respectively.
If this cipher requires any random bytes (for example for an * initilization vector) then the {@link java.security.SecureRandom} * with the highest priority is used as the source of these bytes.
* *A call to any of the init
methods overrides the
* state of the instance, and is equivalent to creating a new instance
* and calling its init
method.
Initialize this cipher with the supplied key and parameters.
* *The cipher will be initialized for encryption, decryption, key
* wrapping, or key unwrapping, depending upon whether the
* opmode
argument is {@link #ENCRYPT_MODE}, {@link
* #DECRYPT_MODE}, {@link #WRAP_MODE}, or {@link #UNWRAP_MODE},
* respectively.
If this cipher requires any random bytes (for example for an * initilization vector) then the {@link java.security.SecureRandom} * with the highest priority is used as the source of these bytes.
* *A call to any of the init
methods overrides the
* state of the instance, and is equivalent to creating a new instance
* and calling its init
method.
Initialize this cipher with the supplied key, parameters, and * source of randomness.
* *The cipher will be initialized for encryption, decryption, key
* wrapping, or key unwrapping, depending upon whether the
* opmode
argument is {@link #ENCRYPT_MODE}, {@link
* #DECRYPT_MODE}, {@link #WRAP_MODE}, or {@link #UNWRAP_MODE},
* respectively.
A call to any of the init
methods overrides the
* state of the instance, and is equivalent to creating a new instance
* and calling its init
method.
Initialize this cipher with the supplied key, parameters, and * source of randomness.
* *The cipher will be initialized for encryption, decryption, key
* wrapping, or key unwrapping, depending upon whether the
* opmode
argument is {@link #ENCRYPT_MODE}, {@link
* #DECRYPT_MODE}, {@link #WRAP_MODE}, or {@link #UNWRAP_MODE},
* respectively.
A call to any of the init
methods overrides the
* state of the instance, and is equivalent to creating a new instance
* and calling its init
method.
wrappedKey
* is not a wrapped key, if the algorithm cannot unwrap this
* key, or if the unwrapped key's type differs from the
* specified type.
* @throws java.security.NoSuchAlgorithmException If
* wrappedKeyAlgorithm
is not a valid algorithm
* name.
*/
public final Key unwrap(byte[] wrappedKey, String wrappedKeyAlgorithm,
int wrappedKeyType)
throws IllegalStateException, InvalidKeyException, NoSuchAlgorithmException
{
if (cipherSpi == null)
{
return null;
}
if (state != UNWRAP_MODE)
{
throw new IllegalStateException("instance is not for unwrapping");
}
return cipherSpi.engineUnwrap(wrappedKey, wrappedKeyAlgorithm,
wrappedKeyType);
}
/**
* Continue a multi-part transformation on an entire byte array,
* returning the transformed bytes.
*
* @param input The input bytes.
* @return The transformed bytes.
* @throws java.lang.IllegalStateException If this cipher was not
* initialized for encryption or decryption.
*/
public final byte[] update(byte[] input) throws IllegalStateException
{
return update(input, 0, input.length);
}
/**
* Continue a multi-part transformation on part of a byte array,
* returning the transformed bytes.
*
* @param input The input bytes.
* @param inputOffset The index in the input to start.
* @param inputLength The number of bytes to transform.
* @return The transformed bytes.
* @throws java.lang.IllegalStateException If this cipher was not
* initialized for encryption or decryption.
*/
public final byte[] update(byte[] input, int inputOffset, int inputLength)
throws IllegalStateException
{
if (cipherSpi == null)
{
byte[] b = new byte[inputLength];
System.arraycopy(input, inputOffset, b, 0, inputLength);
return b;
}
if (state != ENCRYPT_MODE && state != DECRYPT_MODE)
{
throw new IllegalStateException(
"cipher is not for encrypting or decrypting");
}
return cipherSpi.engineUpdate(input, inputOffset, inputLength);
}
/**
* Continue a multi-part transformation on part of a byte array,
* placing the transformed bytes into the given array.
*
* @param input The input bytes.
* @param inputOffset The index in the input to start.
* @param inputLength The number of bytes to transform.
* @param output The output byte array.
* @return The number of transformed bytes.
* @throws java.lang.IllegalStateException If this cipher was not
* initialized for encryption or decryption.
* @throws javax.security.ShortBufferException If there is not enough
* room in the output array to hold the transformed bytes.
*/
public final int update(byte[] input, int inputOffset, int inputLength,
byte[] output)
throws IllegalStateException, ShortBufferException
{
return update(input, inputOffset, inputLength, output, 0);
}
/**
* Continue a multi-part transformation on part of a byte array,
* placing the transformed bytes into the given array.
*
* @param input The input bytes.
* @param inputOffset The index in the input to start.
* @param inputLength The number of bytes to transform.
* @param output The output byte array.
* @param outputOffset The index in the output array to start.
* @return The number of transformed bytes.
* @throws java.lang.IllegalStateException If this cipher was not
* initialized for encryption or decryption.
* @throws javax.security.ShortBufferException If there is not enough
* room in the output array to hold the transformed bytes.
*/
public final int update(byte[] input, int inputOffset, int inputLength,
byte[] output, int outputOffset)
throws IllegalStateException, ShortBufferException
{
if (cipherSpi == null)
{
if (inputLength > output.length - outputOffset)
{
throw new ShortBufferException();
}
System.arraycopy(input, inputOffset, output, outputOffset, inputLength);
return inputLength;
}
if (state != ENCRYPT_MODE && state != DECRYPT_MODE)
{
throw new IllegalStateException(
"cipher is not for encrypting or decrypting");
}
return cipherSpi.engineUpdate(input, inputOffset, inputLength,
output, outputOffset);
}
/**
* Wrap a key.
*
* @param key The key to wrap.
* @return The wrapped key.
* @throws java.lang.IllegalStateException If this instance was not
* initialized for key wrapping.
* @throws javax.crypto.IllegalBlockSizeException If this instance has
* no padding and the key is not a multiple of the block size.
* @throws java.security.InvalidKeyException If this instance cannot
* wrap this key.
*/
public final byte[] wrap(Key key)
throws IllegalStateException, IllegalBlockSizeException, InvalidKeyException
{
if (cipherSpi == null)
{
return null;
}
if (state != WRAP_MODE)
{
throw new IllegalStateException("instance is not for key wrapping");
}
return cipherSpi.engineWrap(key);
}
}