| /******************************************************************************* |
| * Copyright (c) 2000, 2003 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.core.internal.runtime; |
| |
| 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 password |
| */ |
| 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 mode) throws Exception { |
| byte[] result = nextRandom(len); |
| for (int i = 0; i < len; ++i) { |
| result[i] = (byte) (data[i + off] + mode * result[i]); |
| } |
| return result; |
| } |
| } |