// crypt.java // ------------------------------------- // (C) by Michael Peter Christen; mc@anomic.de // first published on http://www.anomic.de // Frankfurt, Germany, 2004 // last major change: 13.05.2004 // // This program 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 of the License, or // (at your option) any later version. // // This program 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 program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // Using this software in any meaning (reading, learning, copying, compiling, // running) means that you agree that the Author(s) is (are) not responsible // for cost, loss of data or any harm that may be caused directly or indirectly // by usage of this softare or this documentation. The usage of this software // is on your own risk. The installation and usage (starting/running) of this // software may allow other people or application to access your computer and // any attached devices and is highly dependent on the configuration of the // software which must be done by the user of the software; the author(s) is // (are) also not responsible for proper configuration and usage of the // software, even if provoked by documentation provided together with // the software. // // Any changes to this file according to the GPL as documented in the file // gpl.txt aside this file in the shipment you received can be done to the // lines that follows this copyright notice here, but changes must not be // done inside the copyright notive above. A re-distribution must contain // the intact and unchanged copyright notice. // Contributions and changes to the program code must be marked as such. package de.anomic.tools; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.security.Provider; import java.security.Security; import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.Locale; import java.util.Random; import java.util.Set; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; import javax.crypto.CipherOutputStream; import javax.crypto.IllegalBlockSizeException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; import de.anomic.server.serverCodings; public class cryptbig { // -------------------------------------------------------- // Section: random salt generation // -------------------------------------------------------- private static long saltcounter = 0; private static Random saltrandom = new Random(System.currentTimeMillis()); public static String randomSalt() { // generate robust 48-bit random number long salt = (saltrandom.nextLong() & 0XffffffffffffL) + (System.currentTimeMillis() & 0XffffffffffffL) + ((1001 * saltcounter) & 0XffffffffffffL); saltcounter++; // we generate 48-bit salt values, that are represented as 8-character b64-encoded strings return serverCodings.standardCoder.encodeBase64Long(salt & 0XffffffffffffL, 8); } // -------------------------------------------------------- // Section: PBE + PublicKey based on passwords encryption // -------------------------------------------------------- public static final String vDATE = "20030925"; public static final String copyright = "[ 'crypt' v" + vDATE + " by Michael Christen / www.anomic.de ]"; public static final String magicString = "crypt|anomic.de|0"; // magic identifier inside every '.crypt' - file public static final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMddHHmmssSSS", Locale.ENGLISH); String cryptMethod; // one of ["TripleDES", "Blowfish", "DESede", "DES"] private static final String defaultMethod = "PBEWithMD5AndDES"; //"DES"; Cipher ecipher; Cipher dcipher; public cryptbig(String pbe) { // this is possible, but not recommended this(pbe, (pbe + "XXXXXXXX").substring(0, 8)); } public cryptbig(String pbe, String salt) { this(pbe, salt, defaultMethod); } private cryptbig(String pbe, String salt, String method) { // a Password-Based Encryption. The SecretKey is created on the fly PBEKeySpec keySpec = new PBEKeySpec(pbe.toCharArray()); try { if (salt.length() > 8) salt = salt.substring(0,8); if (salt.length() < 8) salt = (salt + "XXXXXXXX").substring(0,8); // create the PBE key SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(method); SecretKey key = keyFactory.generateSecret(keySpec); // create parameter spec for PBE PBEParameterSpec paramSpec = new PBEParameterSpec(salt.getBytes(), 1000 /*ITERATIONS*/); // Create a cipher and initialize it for encrypting end decrypting cryptMethod = method; ecipher = Cipher.getInstance(cryptMethod); dcipher = Cipher.getInstance(cryptMethod); ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec); // paramSpec only for PBE! dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec); } catch (javax.crypto.NoSuchPaddingException e) { } catch (java.security.InvalidKeyException e) { } catch (java.security.NoSuchAlgorithmException e) { } catch (java.security.spec.InvalidKeySpecException e) { } catch (java.security.InvalidAlgorithmParameterException e) { } } // Encode a string into a new string using utf-8, crypt and b64 public String encryptString(String str) { try { byte[] utf = str.getBytes("UTF8"); byte[] enc = encryptArray(utf); if (enc == null) return null; return serverCodings.standardCoder.encodeBase64(enc); } catch (UnsupportedEncodingException e) { } catch (java.io.IOException e) { } return null; } // Decode a string into a new string using b64, crypt and utf-8 public String decryptString(String str) { try { byte[] b64dec = serverCodings.standardCoder.decodeBase64(str); if (b64dec == null) return null; // error in input string (inconsistency) byte[] dec = decryptArray(b64dec); if (dec == null) return null; return new String(dec, "UTF8"); } catch (UnsupportedEncodingException e) { } catch (java.io.IOException e) { } return null; } // Encode a byte array into a new byte array public byte[] encryptArray(byte[] b) { if (b == null) return null; try { return ecipher.doFinal(b); } catch (javax.crypto.BadPaddingException e) { } catch (IllegalBlockSizeException e) { } return null; } // Decode a string into a new string using b64, crypt and utf-8 public byte[] decryptArray(byte[] b) { if (b == null) return null; try { return dcipher.doFinal(b); } catch (javax.crypto.BadPaddingException e) { } catch (IllegalBlockSizeException e) { } return null; } // This method returns the available implementations for a service type public static Set listCryptoMethods(String serviceType) { Set result = new HashSet(); // All providers Provider[] providers = Security.getProviders(); for (int i = 0; i < providers.length; i++) { // Get services provided by each provider Set keys = providers[i].keySet(); for (Iterator it = keys.iterator(); it.hasNext(); ) { String key = (String) it.next(); key = key.split(" ")[0]; if (key.startsWith(serviceType + ".")) { result.add(key.substring(serviceType.length() + 1)); } else if (key.startsWith("Alg.Alias." + serviceType + ".")) { // This is an alias result.add(key.substring(serviceType.length() + 11)); } } } return result; } public static void testCryptMethods(Set methods) { String method; Iterator i = methods.iterator(); while (i.hasNext()) { method = (String) i.next(); System.out.print(method + " : "); try { cryptbig crypter = new cryptbig("abrakadabra", method); String encrypted = crypter.encryptString("nicht verraten abc 1234567890"); System.out.print(encrypted + "/"); String decrypted = crypter.decryptString(encrypted); System.out.println(decrypted); } catch (Exception e) { System.out.println("Exception: " + e.getMessage()); e.printStackTrace(); } } } public void encryptFile(String inFileName, String outFileName, boolean compress) { /* File-Format of encrypted file: Filename: b64-of-encryption-of- plus extension ".crypt" File Content: */ try { File inFile = new File(inFileName); String inFileDate = dateFormatter.format(new Date(inFile.lastModified())); // 17 byte String encryptionDate = dateFormatter.format(new Date()); // 17 byte String inFileSize = serverCodings.standardCoder.encodeBase64Long(inFile.length(), 11); // 64 / 6 = 11; 11 byte String flag = "1"; // 1 byte int inFileNameLength = inFileName.length(); // 256 String X = inFileDate + encryptionDate + inFileSize + flag + inFileName; System.out.println("TEST: preserving inFileDate : " + dateFormatter.parse(inFileDate, new ParsePosition(0))); System.out.println("TEST: preserving encryptionDate: " + dateFormatter.parse(encryptionDate, new ParsePosition(0))); System.out.println("TEST: preserving inFileLength : " + inFile.length()); System.out.println("TEST: preserving flag : " + flag); System.out.println("TEST: preserving inFileName : " + inFileName); System.out.println("TEST: preserving X-String : " + X); // start encryption InputStream fin = new CipherInputStream(new FileInputStream(inFile), ecipher); OutputStream fout = new FileOutputStream(outFileName); // write magic and properties of original file // - we encrypt the original date, the encryption date, the file size, the flag // and file name together to the string A and calculate the length AL of that string // - the length of the file name is therefore equal to AL-(17+17+11+1) = AL-46 // - AL is then b64-ed and also encrypted which results into string B // - the length of B is BL; BL is then b64-ed to a string C of fixed length 1 // - after the magic String we write C, B and A try { String A = new String(ecipher.doFinal(X.getBytes("UTF8"))); String B = new String(ecipher.doFinal(serverCodings.standardCoder.encodeBase64Long((long) A.length(), 2).getBytes("UTF8"))); // most probable not longer than 4 String C = serverCodings.standardCoder.encodeBase64Long((long) B.length(), 1); // fixed length 1 (6 bits, that should be enough) fout.write(magicString.getBytes()); // the magic string, used to identify a 'crypt'-file fout.write(C.getBytes()); fout.write(B.getBytes()); fout.write(A.getBytes()); // write content of file copy(fout, fin, 512); } catch (javax.crypto.IllegalBlockSizeException e) {System.err.println("ERROR:" + e.getMessage());} catch (javax.crypto.BadPaddingException e) {System.err.println("ERROR:" + e.getMessage());} // finished files fin.close(); fout.close(); } catch (FileNotFoundException e) { System.err.println("ERROR: file '" + inFileName + "' not found"); } catch (IOException e) { System.err.println("ERROR: IO trouble"); } } public void decryptFile(String inFileName, String outFileName) { InputStream fin = null; OutputStream fout = null; try { // Start opening the files fin = new BufferedInputStream(new FileInputStream(inFileName), 4096); // read the file properties byte[] thisMagic = new byte[magicString.length()]; fin.read(thisMagic); if (!((new String(thisMagic)).equals(magicString))) { // this is not an crypt file, so dont do anything fin.close(); return; } byte[] C = new byte[1]; fin.read(C); // the length of the following String, encoded as b64 byte[] B = new byte[(int) serverCodings.standardCoder.decodeBase64Long(new String(C))]; fin.read(B); // this is again the length of the following string, as encrypted b64-ed integer byte[] A = new byte[(int) serverCodings.standardCoder.decodeBase64Long(new String(dcipher.doFinal(B), "UTF8"))]; fin.read(A); String X = new String(dcipher.doFinal(A), "UTF8"); System.out.println("TEST: detecting X-String : " + X); // reconstruct the properties Date inFileDate = dateFormatter.parse(X.substring(0, 17), new ParsePosition(0)); Date encryptionDate = dateFormatter.parse(X.substring(17, 34), new ParsePosition(0)); long inFileSize = serverCodings.standardCoder.decodeBase64Long(X.substring(34, 45)); String flag = X.substring(45, 46); String origFileName = X.substring(46); System.out.println("TEST: detecting inFileDate : " + inFileDate); System.out.println("TEST: detecting encryptionDate: " + encryptionDate); System.out.println("TEST: detecting inFileLength : " + inFileSize); System.out.println("TEST: detecting flag : " + flag); System.out.println("TEST: detecting inFileName : " + origFileName); // open the output file fout = new BufferedOutputStream(new CipherOutputStream(new FileOutputStream(outFileName), dcipher), 4096); // read and decrypt the file copy(fout, fin, 512); // close the files fin.close(); fout.close(); // do postprocessing } catch (BadPaddingException e) { System.err.println("ERROR: decryption of '" + inFileName + "' not possible: " + e.getMessage()); } catch (IllegalBlockSizeException e) { System.err.println("ERROR: decryption of '" + inFileName + "' not possible: " + e.getMessage()); } catch (FileNotFoundException e) { System.err.println("ERROR: file '" + inFileName + "' not found"); } catch (IOException e) { System.err.println("ERROR: IO trouble"); try {fin.close(); fout.close();} catch (Exception ee) {} } } private static void copy(OutputStream out, InputStream in, int bufferSize) throws IOException { InputStream bIn = new BufferedInputStream(in, bufferSize); OutputStream bOut = new BufferedOutputStream(out, bufferSize); byte buf [] = new byte[bufferSize]; int n; while ((n = bIn.read(buf)) > 0) bOut.write(buf, 0, n); bIn.close(); bOut.close(); } public static String scrambleString(String key, String s) { // we perform several operations // - generate salt // - gzip string // - crypt string with key and salt // - base64-encode result // - attach salt and return String salt = randomSalt(); //System.out.println("Salt=" + salt); cryptbig c = new cryptbig(key, salt); boolean gzFlag = true; byte[] gz = gzip.gzipString(s); if (gz.length > s.length()) { // revert compression try { gz = s.getBytes("UTF8"); gzFlag = false; } catch (UnsupportedEncodingException e) { return null; } } //System.out.println("GZIP length=" + gz.length); if (gz == null) return null; byte[] enc = c.encryptArray(gz); if (enc == null) return null; return salt + ((gzFlag) ? "1" : "0") + serverCodings.enhancedCoder.encodeBase64(enc); } public static String descrambleString(String key, String s) { String salt = s.substring(0, 8); boolean gzFlag = (s.charAt(8) == '1'); s = s.substring(9); cryptbig c = new cryptbig(key, salt); byte[] b64dec = serverCodings.enhancedCoder.decodeBase64(s); if (b64dec == null) return null; // error in input string (inconsistency) byte[] dec = c.decryptArray(b64dec); if (dec == null) return null; if (gzFlag) return gzip.gunzipString(dec); else try {return new String(dec,"UTF8");} catch (UnsupportedEncodingException e) {return null;} } // -------------------------------------------------------- // Section: simple Codings // -------------------------------------------------------- public static String simpleEncode(String content) { return simpleEncode(content, null, 'b'); } public static String simpleEncode(String content, String key) { return simpleEncode(content, key, 'b'); } public static String simpleEncode(String content, String key, char method) { if (key == null) key = "NULL"; if (method == 'p') return "p|" + content; if (method == 'b') return "b|" + serverCodings.enhancedCoder.encodeBase64String(content); if (method == 'z') return "z|" + serverCodings.enhancedCoder.encodeBase64(gzip.gzipString(content)); if (method == 'c') return "c|" + scrambleString(key, content); return null; } public static String simpleDecode(String encoded, String key) { if ((encoded == null) || (encoded.length() < 3)) return null; if (encoded.charAt(1) != '|') return encoded; // not encoded char method = encoded.charAt(0); encoded = encoded.substring(2); if (method == 'p') return encoded; if (method == 'b') return serverCodings.enhancedCoder.decodeBase64String(encoded); if (method == 'z') return gzip.gunzipString(serverCodings.enhancedCoder.decodeBase64(encoded)); if (method == 'c') return descrambleString(key, encoded); return null; } // -------------------------------------------------------- // Section: one-way encryption // -------------------------------------------------------- public static String oneWayEncryption(String key) { cryptbig crypter = new cryptbig(key); String e = crypter.encryptString(key); if (e.length() == 0) e = "0XXXX"; if (e.length() % 2 == 1) e += "X"; while (e.length() < 32) e = e + e; char[] r = new char[16]; for (int i = 0; i < 16; i++) r[i] = e.charAt(2 * i + 1); return new String(r); } // -------------------------------------------------------- // Section: command interface // -------------------------------------------------------- private static void help() { System.out.println("AnomicCrypt (2003) by Michael Christen"); System.out.println("Password-based encryption using the " + defaultMethod + "-method in standard java"); System.out.println("usage: crypt -h | -help"); System.out.println(" crypt -1 "); System.out.println(" crypt -md5 "); System.out.println(" crypt ( -es64 | -ds64 | -ec64 | -dc64 ) "); System.out.println(" crypt ( -e | -d ) "); System.out.println(" crypt -enc \\"); System.out.println(" [-o | -preserveFilename] \\"); System.out.println(" [-d | -preserveDate] [-noZip]"); System.out.println(" crypt -dec \\"); System.out.println(" [-o | -preserveFilename] \\"); System.out.println(" [-d | -preserveDate]"); System.out.println(" crypt ( -info | -name | -size | -date | -edate ) \\"); System.out.println(" "); } private static void longhelp() { // --line-help-- *--------------------------------------------------------------- System.out.println("AnomicCrypt (2003) by Michael Christen"); System.out.println(""); System.out.println(""); System.out.println("crypt -1 "); System.out.println(""); System.out.println(" One-way encryption of the given password."); System.out.println(" The result is computed by encoding the word with the word as"); System.out.println(" the password and repeating it until the length is greater"); System.out.println(" than 32. Then every second character is taken to compose the"); System.out.println(" result which has always the length of 16 characters."); System.out.println(""); System.out.println(""); System.out.println("crypt -md5 "); System.out.println(""); System.out.println(" MD5 digest according to RFC 1321. The resulting bytes are"); System.out.println(" encoded as two-digit hex and concatenated to a single string."); System.out.println(""); System.out.println(""); System.out.println("crypt -ec64 "); System.out.println(""); System.out.println(" Encoding of a cardianal (a positive long integer) with the"); System.out.println(" built-in non-standard base-64 algorithm."); System.out.println(""); System.out.println(""); System.out.println("crypt -dc64 "); System.out.println(""); System.out.println(" Decoding of the given b64-coded string to a cardinal number."); System.out.println(""); System.out.println(""); System.out.println("crypt -es64 "); System.out.println(""); System.out.println(" Encoding of a given String to a b64 string."); System.out.println(""); System.out.println(""); System.out.println("crypt -ds64 "); System.out.println(""); System.out.println(" Decoding of a given b64-coded string to a normal string."); System.out.println(""); System.out.println(""); System.out.println("crypt -e "); System.out.println(""); System.out.println(" Encryption of a given Unicode-String."); System.out.println(" The given string is first encoded to an UTF-8 byte stream, then"); System.out.println(" encoded using a password based encryption and then finaly"); System.out.println(" encoded to b64 to generate a printable form."); System.out.println(" The PBE method is " + defaultMethod + "."); System.out.println(""); System.out.println(""); System.out.println("crypt -d "); System.out.println(""); System.out.println(" Decryption of a string."); System.out.println(" The string is b64-decoded, " + defaultMethod + "-decrypted, "); System.out.println(" and then transformed to an unicode string."); System.out.println(""); System.out.println(""); System.out.println("crypt -enc \\"); System.out.println(" [-o | -preserveFilename] \\"); System.out.println(" [-d | -preserveDate] [-noZip]"); System.out.println(""); System.out.println(""); System.out.println(""); System.out.println(""); System.out.println("crypt -dec \\"); System.out.println(" [-o | -preserveFilename] \\"); System.out.println(" [-d | -preserveDate]"); System.out.println(""); System.out.println(""); System.out.println("crypt ( -info | -name | -size | -date | -edate ) "); System.out.println(""); System.out.println(""); } public static void main(String[] s) { if (s.length == 0) { help(); System.exit(0); } if ((s[0].equals("-h")) || (s[0].equals("-help"))) { longhelp(); System.exit(0); } if (s[0].equals("-tc")) { // list all available crypt mehtods: Set methods = listCryptoMethods("Cipher"); System.out.println(methods.size() + " crypt methods:" + methods.toString()); testCryptMethods(methods); System.exit(0); } if (s[0].equals("-random")) { int count = ((s.length == 2) ? (Integer.parseInt(s[1])) : 1); for (int i = 0; i < count; i++) System.out.println(randomSalt()); System.exit(0); } if (s[0].equals("-1")) { if (s.length != 2) {help(); System.exit(-1);} System.out.println(oneWayEncryption(s[1])); System.exit(0); } if (s[0].equals("-ec64")) { // generate a b64 encoding from a given cardinal if (s.length != 2) {help(); System.exit(-1);} System.out.println(serverCodings.standardCoder.encodeBase64Long(Long.parseLong(s[1]), 0)); System.exit(0); } if (s[0].equals("-dc64")) { // generate a b64 decoding from a given cardinal if (s.length != 2) {help(); System.exit(-1);} System.out.println(serverCodings.standardCoder.decodeBase64Long(s[1])); System.exit(0); } if (s[0].equals("-es64")) { // generate a b64 encoding from a given string if (s.length != 2) {help(); System.exit(-1);} System.out.println(serverCodings.standardCoder.encodeBase64String(s[1])); System.exit(0); } if (s[0].equals("-ds64")) { // generate a b64 decoding from a given string if (s.length != 2) {help(); System.exit(-1);} System.out.println(serverCodings.standardCoder.decodeBase64String(s[1])); System.exit(0); } if (s[0].equals("-ess")) { // 'scramble' string if (s.length != 3) {help(); System.exit(-1);} long t = System.currentTimeMillis(); System.out.println(scrambleString(s[1], s[2])); System.out.println("Calculation time: " + (System.currentTimeMillis() - t) + " milliseconds"); System.exit(0); } if (s[0].equals("-dss")) { // 'descramble' string if (s.length != 3) {help(); System.exit(-1);} long t = System.currentTimeMillis(); System.out.println(descrambleString(s[1], s[2])); System.out.println("Calculation time: " + (System.currentTimeMillis() - t) + " milliseconds"); System.exit(0); } if (s[0].equals("-e")) { if (s.length != 3) {help(); System.exit(-1);} System.out.println((new cryptbig(s[1])).encryptString(s[2])); System.exit(0); } if (s[0].equals("-d")) { if (s.length != 3) {help(); System.exit(-1);} System.out.println((new cryptbig(s[1])).decryptString(s[2])); System.exit(0); } if (s[0].equals("-md5")) { // generate a public key from a password that can be used for encryption if (s.length != 2) {help(); System.exit(-1);} String md5s = serverCodings.encodeMD5Hex(new File(s[1])); System.out.println(md5s); System.exit(0); } if (s[0].equals("-enc")) { if ((s.length < 3) || (s.length > 4)) {help(); System.exit(-1);} String target; if (s.length == 3) target = s[2] + ".crypt"; else target = s[3]; (new cryptbig(s[1])).encryptFile(s[2], target, true /*compress*/); System.exit(0); } if (s[0].equals("-dec")) { if ((s.length < 3) || (s.length > 4)) {help(); System.exit(-1);} String target; if (s.length == 3) { if (s[2].endsWith(".crypt")) target = s[2].substring(0, s[2].length() - 7); else target = s[2] + ".decoded"; } else { target = s[3]; } (new cryptbig(s[1])).decryptFile(s[2], target); System.exit(0); } help(); System.exit(-1); } }