// cryptbig.java // ------------------------------------- // (C) by Michael Peter Christen; mc@yacy.net // first published on http://www.anomic.de // Frankfurt, Germany, 2004 // // $LastChangedDate$ // $LastChangedRevision$ // $LastChangedBy$ // // 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 package net.yacy.utils; 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 net.yacy.cora.document.encoding.UTF8; import net.yacy.cora.order.Base64Order; import net.yacy.cora.order.Digest; import net.yacy.cora.util.CommonPattern; import net.yacy.cora.util.ConcurrentLog; 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 final 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 Base64Order.standardCoder.encodeLongSB(salt & 0XffffffffffffL, 8).toString(); } // -------------------------------------------------------- // 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(final String pbe) { // this is possible, but not recommended this(pbe, (pbe + "XXXXXXXX").substring(0, 8)); } public cryptbig(final String pbe, final String salt) { this(pbe, salt, defaultMethod); } private cryptbig(final String pbe, String salt, final String method) { // a Password-Based Encryption. The SecretKey is created on the fly final 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 final SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(method); final SecretKey key = keyFactory.generateSecret(keySpec); // create parameter spec for PBE final PBEParameterSpec paramSpec = new PBEParameterSpec(UTF8.getBytes(salt), 1000 /*ITERATIONS*/); // Create a cipher and initialize it for encrypting end decrypting this.cryptMethod = method; this.ecipher = Cipher.getInstance(this.cryptMethod); this.dcipher = Cipher.getInstance(this.cryptMethod); this.ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec); // paramSpec only for PBE! this.dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec); } catch (final javax.crypto.NoSuchPaddingException e) { } catch (final java.security.InvalidKeyException e) { } catch (final java.security.NoSuchAlgorithmException e) { } catch (final java.security.spec.InvalidKeySpecException e) { } catch (final java.security.InvalidAlgorithmParameterException e) { } } // Encode a string into a new string using utf-8, crypt and b64 public String encryptString(final String str) { try { final byte[] utf = str.getBytes("UTF8"); final byte[] enc = encryptArray(utf); if (enc == null) return null; return Base64Order.standardCoder.encode(enc); } catch (final UnsupportedEncodingException e) { } return null; } // Decode a string into a new string using b64, crypt and utf-8 public String decryptString(final String str) { final byte[] b64dec = Base64Order.standardCoder.decode(str); if (b64dec == null) return null; // error in input string (inconsistency) final byte[] dec = decryptArray(b64dec); if (dec == null) return null; return UTF8.String(dec); } // Encode a byte array into a new byte array public byte[] encryptArray(final byte[] b) { if (b == null) return null; try { return this.ecipher.doFinal(b); } catch (final javax.crypto.BadPaddingException e) { } catch (final IllegalBlockSizeException e) { } return null; } // Decode a string into a new string using b64, crypt and utf-8 public byte[] decryptArray(final byte[] b) { if (b == null) return null; try { return this.dcipher.doFinal(b); } catch (final javax.crypto.BadPaddingException e) { } catch (final IllegalBlockSizeException e) { } return null; } // This method returns the available implementations for a service type public static Set<String> listCryptoMethods(final String serviceType) { final Set<String> result = new HashSet<String>(); // All providers final Provider[] providers = Security.getProviders(); for (Provider provider : providers) { // Get services provided by each provider final Set<?> keys = provider.keySet(); for (Object name : keys) { String key = (String) name; key = CommonPattern.SPACE.split(key)[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(final Set<String> methods) { String method; final Iterator<String> i = methods.iterator(); while (i.hasNext()) { method = i.next(); System.out.print(method + " : "); try { final cryptbig crypter = new cryptbig("abrakadabra", method); final String encrypted = crypter.encryptString("nicht verraten abc 1234567890"); System.out.print(encrypted + "/"); final String decrypted = crypter.decryptString(encrypted); System.out.println(decrypted); } catch (final Exception e) { System.out.println("Exception: " + e.getMessage()); ConcurrentLog.logException(e); } } } public void encryptFile(final String inFileName, final String outFileName) { /* File-Format of encrypted file: Filename: b64-of-encryption-of-<encryption-date: YYYYMMddHHmmSSsss> plus extension ".crypt" File Content: <original file name> <original file date> <original file size> <compressed-before-encryption-flag> <compressed-after-encryption-flag> <binary> */ try { final File inFile = new File(inFileName); final String inFileDate = dateFormatter.format(new Date(inFile.lastModified())); // 17 byte final String encryptionDate = dateFormatter.format(new Date()); // 17 byte final String inFileSize = Base64Order.standardCoder.encodeLongSB(inFile.length(), 11).toString(); // 64 / 6 = 11; 11 byte final String flag = "1"; // 1 byte //int inFileNameLength = inFileName.length(); // 256 final 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 final InputStream fin = new CipherInputStream(new FileInputStream(inFile), this.ecipher); final 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 { final String A = UTF8.String(this.ecipher.doFinal(X.getBytes("UTF8"))); final String B = UTF8.String(this.ecipher.doFinal(Base64Order.standardCoder.encodeLongSB(A.length(), 2).toString().getBytes("UTF8"))); // most probable not longer than 4 final String C = Base64Order.standardCoder.encodeLongSB(B.length(), 1).toString(); // fixed length 1 (6 bits, that should be enough) fout.write(UTF8.getBytes(magicString)); // the magic string, used to identify a 'crypt'-file fout.write(UTF8.getBytes(C)); fout.write(UTF8.getBytes(B)); fout.write(UTF8.getBytes(A)); // write content of file copy(fout, fin, 512); } catch (final javax.crypto.IllegalBlockSizeException e) {System.err.println("ERROR:" + e.getMessage());} catch (final javax.crypto.BadPaddingException e) {System.err.println("ERROR:" + e.getMessage());} // finished files fin.close(); fout.close(); } catch (final FileNotFoundException e) { System.err.println("ERROR: file '" + inFileName + "' not found"); } catch (final IOException e) { System.err.println("ERROR: IO trouble"); } } public void decryptFile(final String inFileName, final String outFileName) { InputStream fin = null; OutputStream fout = null; try { // Start opening the files fin = new BufferedInputStream(new FileInputStream(inFileName), 4096); // read the file properties final byte[] thisMagic = new byte[magicString.length()]; fin.read(thisMagic); if (!((UTF8.String(thisMagic)).equals(magicString))) { // this is not an crypt file, so dont do anything fin.close(); return; } final byte[] C = new byte[1]; fin.read(C); // the length of the following String, encoded as b64 final byte[] B = new byte[(int) Base64Order.standardCoder.decodeLong(UTF8.String(C))]; fin.read(B); // this is again the length of the following string, as encrypted b64-ed integer final byte[] A = new byte[(int) Base64Order.standardCoder.decodeLong(UTF8.String(this.dcipher.doFinal(B)))]; fin.read(A); final String X = UTF8.String(this.dcipher.doFinal(A)); System.out.println("TEST: detecting X-String : " + X); // reconstruct the properties final Date inFileDate = dateFormatter.parse(X.substring(0, 17), new ParsePosition(0)); final Date encryptionDate = dateFormatter.parse(X.substring(17, 34), new ParsePosition(0)); final long inFileSize = Base64Order.standardCoder.decodeLong(X.substring(34, 45)); final String flag = X.substring(45, 46); final 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), this.dcipher), 4096); // read and decrypt the file copy(fout, fin, 512); // close the files fin.close(); fout.close(); // do postprocessing } catch (final BadPaddingException e) { System.err.println("ERROR: decryption of '" + inFileName + "' not possible: " + e.getMessage()); } catch (final IllegalBlockSizeException e) { System.err.println("ERROR: decryption of '" + inFileName + "' not possible: " + e.getMessage()); } catch (final FileNotFoundException e) { System.err.println("ERROR: file '" + inFileName + "' not found"); } catch (final IOException e) { System.err.println("ERROR: IO trouble"); try { if(fin != null) { fin.close(); }} catch (final Exception ee) {} try { if(fout != null) { fout.close(); }} catch (final Exception ee) {} } } private static void copy(final OutputStream out, final InputStream in, final int bufferSize) throws IOException { final InputStream bIn = new BufferedInputStream(in, bufferSize); final OutputStream bOut = new BufferedOutputStream(out, bufferSize); final 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(final String key, final String s) { // we perform several operations // - generate salt // - gzip string // - crypt string with key and salt // - base64-encode result // - attach salt and return final String salt = randomSalt(); //System.out.println("Salt=" + salt); final 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 (final UnsupportedEncodingException e) { return null; } } //System.out.println("GZIP length=" + gz.length); if (gz == null) return null; final byte[] enc = c.encryptArray(gz); if (enc == null) return null; return salt + ((gzFlag) ? "1" : "0") + Base64Order.enhancedCoder.encode(enc); } public static String descrambleString(final String key, String s) { final String salt = s.substring(0, 8); final boolean gzFlag = (s.charAt(8) == '1'); s = s.substring(9); final cryptbig c = new cryptbig(key, salt); final byte[] b64dec = Base64Order.enhancedCoder.decode(s); if (b64dec == null) return null; // error in input string (inconsistency) final byte[] dec = c.decryptArray(b64dec); if (dec == null) return null; if (gzFlag) return gzip.gunzipString(dec); return UTF8.String(dec); } // -------------------------------------------------------- // Section: simple Codings // -------------------------------------------------------- public static String simpleEncode(final String content) { return simpleEncode(content, null, 'b'); } public static String simpleEncode(final String content, final String key) { return simpleEncode(content, key, 'b'); } public static String simpleEncode(final String content, String key, final char method) { if (key == null) key = "NULL"; if (method == 'p') return "p|" + content; if (method == 'b') return "b|" + Base64Order.enhancedCoder.encodeString(content); if (method == 'z') return "z|" + Base64Order.enhancedCoder.encode(gzip.gzipString(content)); if (method == 'c') return "c|" + scrambleString(key, content); return null; } public static String simpleDecode(String encoded, final String key) { if ((encoded == null) || (encoded.length() < 3)) return null; if (encoded.charAt(1) != '|') return encoded; // not encoded final char method = encoded.charAt(0); encoded = encoded.substring(2); if (method == 'p') return encoded; if (method == 'b') return Base64Order.enhancedCoder.decodeString(encoded); if (method == 'z') return gzip.gunzipString(Base64Order.enhancedCoder.decode(encoded)); if (method == 'c') return descrambleString(key, encoded); return null; } // -------------------------------------------------------- // Section: one-way encryption // -------------------------------------------------------- public static String oneWayEncryption(final String key) { final cryptbig crypter = new cryptbig(key); String e = crypter.encryptString(key); if (e.isEmpty()) e = "0XXXX"; if (e.length() % 2 != 0) e += "X"; while (e.length() < 32) e = e + e; final 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 <passwd>"); System.out.println(" crypt -md5 <file>"); System.out.println(" crypt ( -es64 | -ds64 | -ec64 | -dc64 ) <string>"); System.out.println(" crypt ( -e | -d ) <key> <string>"); System.out.println(" crypt -enc <key> <file> \\"); System.out.println(" [-o <target-file> | -preserveFilename] \\"); System.out.println(" [-d <YYYYMMddHHmmSSsss> | -preserveDate] [-noZip]"); System.out.println(" crypt -dec <key> <file> \\"); System.out.println(" [-o <target-file> | -preserveFilename] \\"); System.out.println(" [-d <YYYYMMddHHmmSSsss> | -preserveDate]"); System.out.println(" crypt ( -info | -name | -size | -date | -edate ) \\"); System.out.println(" <key> <encrypted-file>"); } 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 <passwd>"); 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 <file>"); 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 <cardinal>"); 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 <string>"); 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 <string>"); 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 <string>"); 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 <key> <string>"); 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 <key> <string>"); 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 <key> <file> \\"); System.out.println(" [-o <target-file> | -preserveFilename] \\"); System.out.println(" [-d <target-date YYYYMMddHHmmSSsss> | -preserveDate] [-noZip]"); System.out.println(""); System.out.println(""); System.out.println(""); System.out.println(""); System.out.println("crypt -dec <key> <file> \\"); System.out.println(" [-o <target-file> | -preserveFilename] \\"); System.out.println(" [-d <target-date YYYYMMddHHmmSSsss> | -preserveDate]"); System.out.println(""); System.out.println(""); System.out.println("crypt ( -info | -name | -size | -date | -edate ) <key> <encrypted-file>"); System.out.println(""); System.out.println(""); } public static void main(final 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: final Set<String> methods = listCryptoMethods("Cipher"); System.out.println(methods.size() + " crypt methods:" + methods.toString()); testCryptMethods(methods); System.exit(0); } if (s[0].equals("-random")) { final 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(Base64Order.standardCoder.encodeLongSB(Long.parseLong(s[1]), 0).toString()); 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(Base64Order.standardCoder.decodeLong(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(Base64Order.standardCoder.encodeString(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(Base64Order.standardCoder.decodeString(s[1])); System.exit(0); } if (s[0].equals("-ess")) { // 'scramble' string if (s.length != 3) {help(); System.exit(-1);} final 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);} final 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 = ""; try { md5s = Digest.encodeMD5Hex(new File(s[1])); } catch (final IOException e) { e.printStackTrace(); } 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); 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); } }