Java tutorial
/* * Copyright (C) 2007 Helge Hess <helge.hess@opengroupware.org> * * This file is part of Go. * * Go is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any later version. * * Go 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 Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with Go; see the file COPYING. If not, write to the Free Software * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.getobjects.foundation; import java.io.BufferedInputStream; 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.net.URL; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * UData * <p> * Byte array related utility functions. */ public class UData extends NSObject { protected static Log log = LogFactory.getLog("UData"); private UData() { } /* do not allow construction */ /** * This method reads the whole contents of the given stream into a byte array. * After the content is read, the stream is closed. * * @param _in - an InputStream to read the content from * @return a byte[] array containing the stream contents or null on error */ public static byte[] loadContentFromStream(final InputStream _in) { if (_in == null) return null; // TBD: is this necessary? Probably not, we already copy with a bufer final BufferedInputStream in = (_in instanceof BufferedInputStream) ? (BufferedInputStream) _in : new BufferedInputStream(_in); // TODO: there must be a smarter way to do this ;-) byte[] results = new byte[0]; try { int didRead; byte[] buf = new byte[4096]; while ((didRead = in.read(buf)) > 0) { byte[] nre = new byte[results.length + didRead]; System.arraycopy(results, 0 /* start of source */, nre, 0 /* start in dest */, results.length); System.arraycopy(buf, 0 /* start of source */, nre, results.length /* start in dest */, didRead); results = nre; } } catch (IOException e) { log.warn("failed to read data from stream: " + _in, e); return null; } finally { try { in.close(); } catch (IOException e) { log.warn("could not close input stream", e); } } return results; } /** * This method reads the whole contents of the given object into a byte array. * After the content is read, the stream is closed. * <p> * The object can be: * <ul> * <li>an InputStream * <li>a File object * <li>a String treated as a path name * <li>a URL object * </ul> * * @param _o - an InputStream/File/URL/path to read the content from * @return a byte[] array containing the stream contents or null on error */ public static byte[] loadContentFromSource(final Object _o) { if (_o == null) return null; if (_o instanceof InputStream) return loadContentFromStream((InputStream) _o); if (_o instanceof File) { try { /* Note: stream is closed by load function */ FileInputStream is = new FileInputStream((File) _o); return loadContentFromStream(is); } catch (FileNotFoundException e) { log.info("could not open file: " + _o); return null; } } if (_o instanceof String) { try { /* Note: stream is closed by load function */ FileInputStream is = new FileInputStream((String) _o); return loadContentFromStream(is); } catch (FileNotFoundException e) { log.info("could not open file: " + _o); return null; } } if (_o instanceof URL) { try { /* Note: stream is closed by load function */ return loadContentFromStream(((URL) _o).openStream()); } catch (IOException e) { log.info("could not open URL: " + _o, e); return null; } } log.error("don't know how to load data from object: " + _o.getClass()); return null; } /* writing files */ public static Exception writeToStream(byte[] _data, OutputStream _out) { if (_data == null) return new NSException("got no data to write ..."); if (_out == null) return new NSException("got no stream to write data to ..."); try { _out.write(_data, 0, _data.length); _out.flush(); } catch (IOException e) { return e; } return null /* everything went fine */; } public static Exception writeToFile(final byte[] _data, final File _file, final boolean _atomically) { if (_file == null) return new NSException("got no File to write data to ..."); if (_file.isDirectory()) return new NSException("target File is a directory ..."); OutputStream os; File tmpFile = null; /* create tmpfile and open output stream */ if (_atomically) { try { tmpFile = File.createTempFile(".UData", ".atomicwrite", _file.getParentFile()); } catch (IOException e) { return e; } try { os = new FileOutputStream(tmpFile); } catch (FileNotFoundException e) { tmpFile.delete(); return e; } } else { try { os = new FileOutputStream(_file); } catch (FileNotFoundException e) { return e; } } /* write data to stream */ Exception error = writeToStream(_data, os); try { if (os != null) os.close(); } catch (IOException e) { log.warn("could not close output stream: " + os, e); os = null; } if (error != null) { if (tmpFile != null) tmpFile.delete(); return error; } if (tmpFile == null) { /* OK, we did a non atomic write and everything went fine. Great! */ return null; } /* move file on atomic writes, depending on the host OS this is not really * atomic */ /* first attempt to move the file "over" the old one */ if (tmpFile.renameTo(_file)) return null; /* rename went fine */ /* If this didn't work out, check whether the target exists and * delete it prior moving the tmpfile over it */ if (_file.exists()) { if (!_file.delete()) { // TBD: should we attempt to copy in this situation? tmpFile.delete(); return new NSException("Could not delete target file"); } if (tmpFile.renameTo(_file)) return null; /* rename went fine */ // TBD: should we attempt to copy in this situation? return new NSException("Could not move temporary data file!"); } return new NSException("Could not move temporary data file!"); } public static Exception writeToFile(final byte[] _data, final String _path, final boolean _atomically) { if (_data == null) return new NSException("got no data to write ..."); if (_path == null) return new NSException("got no path to write data to ..."); return writeToFile(_data, new File(_path), _atomically); } /* hashing */ /** * Calculates an MD5 hash over the given byte array and returns it as a * String containing hex digits (eg 0FAADE...). * * @param _p - a byte array * @return a String containing the MD5 hash, or null on error */ public static String md5HashForData(final byte[] _p) { if (_p == null) return null; final String pwdhash = UString.hexStringFromData(md5DataHashForData(_p)); if (pwdhash == null || pwdhash.length() == 0) { log.error("could not compute the MD5 hash of a given byte array."); return null; } return pwdhash; } /** * Calculates an MD5 hash over the given byte array and returns it as a * byte array. * * @param _p - a byte array * @return the MD5 hash, or null on error */ public static byte[] md5DataHashForData(final byte[] _p) { if (_p == null) return null; try { // TODO: cache digest in thread local variable? final MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.update(_p); final byte[] res = md5.digest(); md5.reset(); // TBD: superflous? return res; } catch (NoSuchAlgorithmException e) { log.error("did not find MD5 hash generator", e); return null; } } /* Base64 */ /** * Decode the given String as a BASE64 byte array. * <p> * Reverse is UString.stringByEncodingBase64(byte[]). * * @param _src - the String containing the BASE64. * @return the decoded byte array, or null if decoding failed */ public static byte[] dataByDecodingBase64(String _src) { if (_src == null) return null; try { // TBD: use non-private mechanism?! return new sun.misc.BASE64Decoder().decodeBuffer(_src); } catch (IOException e) { log.error("could not decode base64 string", e); } return null; } /* encryption/decryption */ /** * Encrypts the given data with the given password as MD5 in the given * algorithm (defaults to AES). * The password can be prefixed with the hashing algorithm, eg: * {md5}abcdef * to hash the password as MD5. MD5 is the default if the {} prefix is * missing. * * @param _data - data to be encrypted, may not be null * @param _password - will be converted to a hash * @param _algo - algorithm to use for encryption (eg AES or DES) * @return the encrypted array of bytes, or null if something failed */ public static byte[] encrypt(byte[] _data, String _password, String _algo) { if (_password == null || _data == null || _password.length() == 0) return null; byte[] hash; int idx; if (_password.charAt(0) == '{' && (idx = _password.indexOf('}')) > 1) { String hashAlgo = _password.substring(1, idx); // TBD: use generic mechanism if (hashAlgo.equalsIgnoreCase("md5")) hash = UData.md5DataHashForData(UString.getBytes(_password, null)); else { log.error("unsupported password hash algorithm: " + hashAlgo); return null; } } else hash = UData.md5DataHashForData(UString.getBytes(_password, null)); return UData.encrypt(_data, hash, _algo); } /** * Decrypts the given data with the given password as MD5 in the given * algorithm (defaults to AES). * The password can be prefixed with the hashing algorithm, eg: * {md5}abcdef * to hash the password as MD5. MD5 is the default if the {} prefix is * missing. * * @param _data - data to be decrypted, may not be null * @param _password - will be converted to a hash * @param _algo - algorithm to use for encryption (eg AES or DES) * @return the encrypted array of bytes, or null if something failed */ public static byte[] decrypt(byte[] _data, String _password, String _algo) { if (_password == null || _data == null || _password.length() == 0) return null; byte[] hash; int idx; if (_password.charAt(0) == '{' && (idx = _password.indexOf('}')) > 1) { String hashAlgo = _password.substring(1, idx); // TBD: use generic mechanism if (hashAlgo.equalsIgnoreCase("md5")) hash = UData.md5DataHashForData(UString.getBytes(_password, null)); else { log.error("unsupported password hash algorithm: " + hashAlgo); return null; } } else hash = UData.md5DataHashForData(UString.getBytes(_password, null)); return UData.decrypt(_data, hash, _algo); } /** * Encrypts the given data with the given key in the given algorithm * (defaults to AES). * * @param _data - data to be encrypted, may not be null * @param _key - key used to encrypt * @param _algo - algorithm to use for encryption (eg AES or DES) * @return the encrypted array of bytes, or null if something failed */ public static byte[] encrypt(byte[] _data, byte[] _key, String _algo) { if (_key == null || _data == null) return null; if (_algo == null) _algo = "AES"; final SecretKey key = new SecretKeySpec(_key, _algo); /* encrypt */ Cipher enc = null; try { enc = Cipher.getInstance(_algo); } catch (NoSuchAlgorithmException e) { log.error("no AES available", e); return null; } catch (NoSuchPaddingException e) { log.error("AES padding error", e); return null; } try { enc.init(Cipher.ENCRYPT_MODE, key); } catch (InvalidKeyException e) { log.error("invalid AES key (#" + _key.length + " bytes)", e); return null; } try { return enc.doFinal(_data); } catch (IllegalBlockSizeException e) { log.error("illegal block size when encrypting data", e); return null; } catch (BadPaddingException e) { log.error("bad padding while encrypting data", e); return null; } } /** * Decrypts the given data with the given key in the given algorithm * (defaults to AES). * * @param _data - data to be decrypted, may not be null * @param _key - key used to encrypt * @param _algo - algorithm to use for encryption (eg AES or DES) * @return the encrypted array of bytes, or null if something failed */ public static byte[] decrypt(byte[] _data, byte[] _key, String _algo) { if (_key == null || _data == null) return null; if (_algo == null) _algo = "AES"; final SecretKey key = new SecretKeySpec(_key, _algo); /* encrypt */ Cipher enc = null; try { enc = Cipher.getInstance(_algo); } catch (NoSuchAlgorithmException e) { log.error("no AES available", e); return null; } catch (NoSuchPaddingException e) { log.error("AES padding error", e); return null; } try { enc.init(Cipher.DECRYPT_MODE, key); } catch (InvalidKeyException e) { log.error("invalid AES key (#" + _key.length + " bytes)", e); return null; } try { return enc.doFinal(_data); } catch (IllegalBlockSizeException e) { log.error("illegal block size when decrypting data", e); return null; } catch (BadPaddingException e) { log.error("bad padding while decrypting data", e); return null; } } }