/*
* This file is part of NeverNote
* Copyright 2009 Randy Baumgarte
*
* This file may be licensed under the terms of of the
* GNU General Public License Version 2 (the ``GPL'').
*
* Software distributed under the License is distributed
* on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
* express or implied. See the GPL for the specific language
* governing rights and limitations.
*
* You should have received a copy of the GPL along with this
* program. If not, go to http://www.gnu.org/licenses/gpl.html
* or write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
package cx.fbn.nevernote.evernote;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.zip.CRC32;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.RC2ParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import cx.fbn.nevernote.utilities.Base64;
public class EnCrypt {
// Convert a string of text to a hex string
public static String asHex (byte buf[]) {
StringBuffer strbuf = new StringBuffer(buf.length * 2);
int i;
for (i = 0; i < buf.length; i++) {
if ((buf[i] & 0xff) < 0x10)
strbuf.append("0");
strbuf.append(Long.toString(buf[i] & 0xff, 16));
}
return strbuf.toString();
}
// public static class EnCryptException extends Exception {
// public EnCryptException(String message, Throwable cause) {
// super(message, cause);
// }
// }
/**
* Choose the character set to use for encoding
*/
private Charset getCharset() {
// Just hard-coding choice here
boolean useUtf8 = true;
final Charset charSet;
if(useUtf8) {
charSet = Charset.forName("UTF-8");
} else {
charSet = Charset.defaultCharset();
}
return charSet;
}
// Useful for debugging, but not normally used
@SuppressWarnings("unused")
private byte[] encodeStringOld(String text) {
int len = text.length()+4;
int mod = (len%8);
if (mod>0) {
for (; mod !=0; len++) {
mod = len%8;
}
len--;
}
len = len-4;
StringBuffer textBuffer = new StringBuffer(text);
textBuffer.setLength(len);
// Setup parms for the cipher
String encoded = crcHeader(textBuffer.toString()) +textBuffer;
return encoded.getBytes();
}
/**
* Main changes are
*
* 1. Do padding based on encoded bytes, not string length (some chars -> 2 bytes)
* 2. Use specific named charset
*/
private byte[] encodeStringNew(String text) {
final Charset charSet = getCharset();
// Convert to bytes using given encoding, and align *bytes* to multiple of
// 8, with 4 bytes reserved for the crc
final byte[] bytes = text.getBytes(charSet);
int align8 = (bytes.length + 4) % 8;
int paddingNeeded = 8 - align8;
final byte[] paddedBytes = Arrays.copyOf(bytes, bytes.length + paddingNeeded);
// Now calculate the crc, using the bytes
String crc = crcHeader(paddedBytes);
byte[] crcBytes = crc.getBytes(charSet);
if(crcBytes.length != 4) {
System.err.println("CRC Bytes really should be 4 in length!");
return null;
}
// Now combine crc bytes and string bytes into byte array
// for encryption
byte[] total = new byte[paddedBytes.length + crcBytes.length];
System.arraycopy(crcBytes, 0, total, 0, crcBytes.length);
System.arraycopy(paddedBytes, 0, total, crcBytes.length, paddedBytes.length);
return total;
}
/**
* Same as for encryption: use named charset, and
* @param bytes
* @return
*/
private String decodeBytesNew(byte[] bytes) {
Charset charSet = getCharset();
byte[] crcBytes = Arrays.copyOfRange(bytes, 0, 4);
byte[] textBytes = Arrays.copyOfRange(bytes, 4, bytes.length);
CharBuffer crcChar = charSet.decode(ByteBuffer.wrap(crcBytes));
CharBuffer textChar = charSet.decode(ByteBuffer.wrap(textBytes));
// Get crc of text to see if same
String cryptCRC = crcChar.toString();
String realCRC = crcHeader(textBytes);
if(realCRC.equals(cryptCRC)) {
// Trim nulls at end
while(textChar.get(textChar.limit() - 1) == 0 && textChar.limit() != 0) {
textChar.limit(textChar.limit() - 1);
}
String str = textChar.toString();
return str;
}
return null;
}
/**
* For reference: old version. Useful for debugging
* @param bytes
* @return
*/
@SuppressWarnings("unused")
private String decodeBytesOld(byte[] bytes) {
// We have a result. Separate it into the 4 byte header and the decrypted text
StringBuffer buffer = new StringBuffer(new String(bytes));
String cryptCRC = buffer.substring(0,4);
String clearText = buffer.substring(4);
String realCRC = crcHeader(clearText);
// We need to get the real CRC of the decrypted text
if (realCRC.equalsIgnoreCase(cryptCRC)) {
int endPos = clearText.length();
for (int i=buffer.length()-1; i>=0; i--) {
if (buffer.charAt(i) == 0)
endPos--;
else
i=-1;
}
clearText = clearText.substring(0,endPos);
return clearText;
}
return null;
}
// Encrypte the text and return the base64 string
public String encrypt(String text, String passphrase, int keylen) {
RC2ParameterSpec parm = new RC2ParameterSpec(keylen);
try {
// Get a MD5 for the passphrase
MessageDigest md = MessageDigest.getInstance("MD5");
// NB Use specific Charset
md.update(passphrase.getBytes(getCharset()));
// Setup parms for the cipher
SecretKeySpec skeySpec = new SecretKeySpec(md.digest(), "RC2");
Cipher cipher = Cipher.getInstance("RC2/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, parm);
//byte[] oldBytes = encodeStringOld(text);
byte[] newBytes = encodeStringNew(text);
//boolean areSame = Arrays.equals(oldBytes, newBytes);
//System.out.println("Same? " + areSame);
byte[] d = cipher.doFinal(newBytes);
return Base64.encodeBytes(d);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return null;
}
// Decrypt the base64 text and return the unsecure text
public String decrypt(String text, String passphrase, int keylen) {
RC2ParameterSpec parm = new RC2ParameterSpec(keylen);
MessageDigest md;
try {
// Get a MD5 for the passphrase
md = MessageDigest.getInstance("MD5");
md.update(passphrase.getBytes(getCharset()));
// Setup parms for the cipher
SecretKeySpec skeySpec = new SecretKeySpec(md.digest(), "RC2");
Cipher cipher = Cipher.getInstance("RC2/ECB/NOPADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, parm);
// Decode the encrypted text and decrypt
byte[] dString = Base64.decode(text);
byte[] d = cipher.doFinal(dString);
//String clearTextOld = decodeBytesOld(d);
String clearTextNew = decodeBytesNew(d);
//if(clearTextNew != null) {
// System.out.println("Are same decrypted ? " + clearTextNew.equals(clearTextOld));
//}
return clearTextNew;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
// Utility function to return the CRC header of an encoded string. This is
// used to verify good decryption and put in front of a new encrypted string
private String crcHeader(String text) {
return crcHeader(text.getBytes());
}
private String crcHeader(byte[] bytes) {
CRC32 crc = new CRC32();
crc.update(bytes);
int realCRC = (int)crc.getValue();
// The first 4 chars of the hex string will equal the first
// 4 chars of the decyphered text. If they match we have a
// good password. This is what we return
realCRC = realCRC ^ (-1);
realCRC = realCRC >>> 0;
String hexCRC = Integer.toHexString(realCRC).substring(0,4);
return hexCRC.toString().toUpperCase();
}
}