blob: 7fa97d719b8b6df269ec93c395b16a2fce14d2f2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.core.internal.runtime.auth;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.util.Random;
/**
* <P>Encrypts or decrypts a sequence of bytes. The bytes are decrypted
* by supplying the same password that was given when the bytes were
* encrypted.
* <P>Here is an example showing how to encrypt and then decrypt the
* string "Hello, world!" using the password "music":
* <pre>
* String password = "music";
* byte[] data = "Hello, world!".getBytes("UTF8");
*
* // Encrypt
* Cipher cipher = new Cipher(ENCRYPT_MODE, password);
* byte[] encrypted = cipher.cipher(data);
*
* // Decrypt
* cipher = new Cipher(DECRYPT_MODE, password);
* byte[] decrypted = cipher.cipher(encrypted);
* </pre>
*/
public class Cipher {
public static final int DECRYPT_MODE = -1;
public static final int ENCRYPT_MODE = 1;
private static final int RANDOM_SIZE = 16;
private int mode = 0;
private byte[] password = null;
//the following fields are used for generating a secure byte stream
//used by the decryption algorithm
private byte[] byteStream;
private int byteStreamOffset;
private MessageDigest digest;
private Random random;
private final byte[] toDigest;
/**
* Initializes the cipher with the given mode and password. This method
* must be called first (before any encryption of decryption takes
* place) to specify whether the cipher should be in encrypt or decrypt
* mode and to set the password.
*
* @param mode
* @param passwordString
*/
public Cipher(int mode, String passwordString) {
this.mode = mode;
try {
this.password = passwordString.getBytes("UTF8"); //$NON-NLS-1$
} catch (UnsupportedEncodingException e) {
this.password = passwordString.getBytes();
}
toDigest = new byte[password.length + RANDOM_SIZE];
}
/**
* Encrypts or decrypts (depending on which mode the cipher is in) the
* given data and returns the result.
*
* @param data
* @return the result of encrypting or decrypting the given data
*/
public byte[] cipher(byte[] data) throws Exception {
return transform(data, 0, data.length, mode);
}
/**
* Encrypts or decrypts (depending on which mode the cipher is in) the
* given data and returns the result.
*
* @param data the byte array containg the given data
* @param off the index of the first byte in the given byte array
* to be transformed
* @param len the number of bytes to be transformed
* @return the result of encrypting or decrypting the given data
*/
public byte[] cipher(byte[] data, int off, int len) throws Exception {
return transform(data, off, len, mode);
}
/**
* Encrypts or decrypts (depending on which mode the cipher is in) the
* given byte and returns the result.
*
* @param datum the given byte
* @return the result of encrypting or decrypting the given byte
*/
public byte cipher(byte datum) throws Exception {
byte[] data = {datum};
return cipher(data)[0];
}
/**
* Generates a secure stream of bytes based on the input seed.
* This routine works by combining the input seed with random bytes
* generated by a random number generator, and then computing the
* SHA-1 hash of those bytes.
*/
private byte[] generateBytes() throws Exception {
if (digest == null) {
digest = MessageDigest.getInstance("SHA"); //$NON-NLS-1$
//also seed random number generator based on password
long seed = 0;
for (int i = 0; i < password.length; i++)
//this function is known to give good hash distribution for character data
seed = (seed * 37) + password[i];
random = new Random(seed);
}
//add random bytes to digest array
random.nextBytes(toDigest);
//overlay password onto digest array
System.arraycopy(password, 0, toDigest, 0, password.length);
//compute and return SHA-1 hash of digest array
return digest.digest(toDigest);
}
/**
* Returns a stream of cryptographically secure bytes of the given length.
* The result is deterministically based on the input seed (password).
*/
private byte[] nextRandom(int length) throws Exception {
byte[] nextRandom = new byte[length];
int nextRandomOffset = 0;
while (nextRandomOffset < length) {
if (byteStream == null || byteStreamOffset >= byteStream.length) {
byteStream = generateBytes();
byteStreamOffset = 0;
}
nextRandom[nextRandomOffset++] = byteStream[byteStreamOffset++];
}
return nextRandom;
}
private byte[] transform(byte[] data, int off, int len, int mod) throws Exception {
byte[] result = nextRandom(len);
for (int i = 0; i < len; ++i) {
result[i] = (byte) (data[i + off] + mod * result[i]);
}
return result;
}
}