/*
* Copyright 2014 individual contributors as indicated by the @author
* tags
*
* This 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 3 of the License, or
* (at your option) any later version.
*
* This software 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 this software. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sector67.otp.cipher;
import java.io.UnsupportedEncodingException;
import org.sector67.otp.key.KeyException;
import org.sector67.otp.key.KeyStore;
/**
* A one-time pad cipher implementation.
*
* This particular implementation is meant for encrypting relatively small values and thus loads the
* appropriate portion of the key directly into memory. If there was a need to encrypt longer sequences
* of bytes, a streaming solution would be more appropriate.
*
* The Java Cryptography Extension (JCE) API was considered as a framework for implementing this cipher, but
* was rejected for a few reasons:
*
* 1) JCE is not well designed for long symmetric key encryption like OTP. Although it could be made to work,
* for a simple algorithm like OTP it is overkill for no apparent benefit.
* 2) Using JCE properly requires installing a signed provider jar and modification of the system's java.security
* file. This introduces additional complexity with no substantial gain.
* 3) Some of the goals of this project are transparency and verifiability, implementing a CipherSpi that is
* then wrapped in a Cipher instance reduces both of those, again with no apparent upside.
*
* @author scott.hasse@gmail.com
*/
public class OneTimePadCipher {
private String CHARSET = "UTF-8";
private KeyStore store;
public OneTimePadCipher(KeyStore store) {
this.store = store;
}
/*
* Encrypts a String as UTF-8 bytes given the filename of a key.
*/
public byte[] encrypt(String keyname, String input) throws KeyException, CipherException {
if (input == null) {
throw new CipherException("Cannot encrypt null plain text");
}
if (keyname == null) {
throw new CipherException("Cannot encrypt with a null key name");
}
//convert String to UTF-8 bytes
byte[] inputBytes = null;
try {
inputBytes = input.getBytes(CHARSET);
} catch (UnsupportedEncodingException e) {
throw new CipherException(e);
}
//get the appropriate key
byte[] key = store.getKeyBytesForEncryption(keyname, inputBytes.length);
byte[] encrypted = encrypt(inputBytes, key);
return encrypted;
}
/*
* Decrypts the provided bytes into a Java String given the name of a key. Assumes the data UTF-8 bytes.
*/
public String decrypt(String keyname, int offset, byte[] input) throws KeyException, CipherException {
//decrypt
//get the appropriate key data
byte[] key = store.getKeyBytesForDecryption(keyname, offset, input.length);
byte[] decrypted = decrypt(input, key);
//interpret the result as UTF-8 bytes
String result = null;
try {
result = new String(decrypted, CHARSET);
} catch (UnsupportedEncodingException e) {
throw new CipherException(e);
}
return result;
}
/* Encrypts the String input as UTF-8 binary data */
public byte[] encryptString(String input, byte[] key) throws CipherException {
if (input == null) {
throw new CipherException("Cannot encrypt null plain text");
}
if (key == null) {
throw new CipherException("Cannot encrypt with a null key");
}
byte[] b = null;
try {
b = input.getBytes(CHARSET);
} catch (UnsupportedEncodingException e) {
throw new CipherException(e);
}
byte[] encrypted = encrypt(b, key);
return encrypted;
}
/* Decrypts an input byte[] into a Java String
* This process expects the binary data to be a UTF-8 encoding
*/
public String decryptToString(byte[] input, byte[] key) throws CipherException {
if (input == null) {
throw new CipherException("Cannot decrypt null cipher text");
}
if (key == null) {
throw new CipherException("Cannot decrypt with a null key");
}
String s = null;
byte[] b = decrypt(input, key);
try {
s = new String(b, CHARSET);
} catch (UnsupportedEncodingException e) {
throw new CipherException(e);
}
return s;
}
public byte[] encrypt(byte[] plaintext, byte[] key) throws CipherException {
if (plaintext == null) {
throw new CipherException("Cannot encrypt null plain text");
}
if (key == null) {
throw new CipherException("Cannot encrypt with a null key");
}
if (plaintext.length != key.length) {
throw new CipherException("Cannot encrypt, input byte length [" + plaintext.length + "] is not the same as the key length [" + key.length + "]");
}
byte[] result = new byte[plaintext.length];
for (int i = 0; i < key.length; i++) {
result[i] = (byte) (plaintext[i] ^ key[i]);
}
return result;
}
public byte[] decrypt(byte[] ciphertext, byte[] key) throws CipherException {
if (ciphertext == null) {
throw new CipherException("Cannot decrypt null cipher text");
}
if (key == null) {
throw new CipherException("Cannot decrypt with a null key");
}
if (ciphertext.length != key.length) {
throw new CipherException("Cannot decrypt, input byte length [" + ciphertext.length + "] is not the same as the key length [" + key.length + "]");
}
byte[] result = new byte[ciphertext.length];
for (int i = 0; i < key.length; i++) {
result[i] = (byte) (ciphertext[i] ^ key[i]);
}
return result;
}
}