最近在做项目的时候需要对文件打包加密,jdk1.5以后自身就支持打包加密,很可惜,当前这个项目用的是jdk1.4,没办法,只能在网上找相关的资料,最后在老大的帮助下,找到了解决方案,但是有个确定,文件名不能有中文出现,只能对英文加密,以下是这个工具的全部代码:
import java.util.Random;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.PBEParametersGenerator;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.modes.SICBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
public class AESEncrypter
{
public static final int KEY_SIZE_BIT = 256;
public static final int KEY_SIZE_BYTE = KEY_SIZE_BIT / 8;
public static final int ITERATION_COUNT = 1000;
// --------------------------------------------------------------------------
protected byte[] salt;
protected byte[] encryptionKey;
protected byte[] authenticationCode;
protected byte[] pwVerification;
protected CipherParameters cipherParameters;
protected SICBlockCipher aesCipher;
protected int blockSize;
protected int nonce;
protected HMac mac;
/**
* Setup AES encryption based on pwBytes using WinZipAES approach
* with SALT and pwVerification bytes based on password+salt.
*/
public AESEncrypter( byte[] pwBytes ) {
PBEParametersGenerator generator = new PKCS5S2ParametersGenerator();
this.salt = createSalt();
generator.init( pwBytes, salt, ITERATION_COUNT );
// create 2 byte[16] for two keys and one byte[2] for pwVerification
// 1. encryption / 2. athentication (via HMAC/hash) /
cipherParameters = generator.generateDerivedParameters(KEY_SIZE_BIT*2 + 16);
byte[] keyBytes = ((KeyParameter)cipherParameters).getKey();
this.encryptionKey = new byte[ KEY_SIZE_BYTE ];
System.arraycopy( keyBytes, 0, encryptionKey, 0, KEY_SIZE_BYTE );
this.authenticationCode = new byte[ KEY_SIZE_BYTE ];
System.arraycopy( keyBytes, KEY_SIZE_BYTE, authenticationCode, 0, KEY_SIZE_BYTE );
// based on SALT + PASSWORD (password is probably correct)
this.pwVerification = new byte[ 2 ];
System.arraycopy( keyBytes, KEY_SIZE_BYTE*2, pwVerification, 0, 2 );
// create the first 16 bytes of the key sequence again (using pw+salt)
generator.init( pwBytes, salt, ITERATION_COUNT );
cipherParameters = generator.generateDerivedParameters(KEY_SIZE_BIT);
// checksum added to the end of the encrypted data, update on each encryption call
this.mac = new HMac( new SHA1Digest() );
mac.init( new KeyParameter(authenticationCode) );
this.aesCipher = new SICBlockCipher(new AESEngine());
this.blockSize = aesCipher.getBlockSize();
// incremented on each 16 byte block and used as encryption NONCE (ivBytes)
nonce = 1;
}
/**
* perform pseudo "in-place" encryption
*/
public void encrypt( byte[] in, int length ) {
int pos = 0;
while( pos<in.length && pos<length ) {
encryptBlock( in, pos, length );
pos += blockSize;
}
}
/**
* encrypt 16 bytes (AES standard block size) or less
* starting at "pos" within "in" byte[]
*/
public void encryptBlock( byte[] in, int pos, int length ) {
byte[] encryptedIn = new byte[blockSize];
byte[] ivBytes = ByteArrayHelper.toLEByteArray( nonce++, 16 );
ParametersWithIV ivParams = new ParametersWithIV(cipherParameters, ivBytes);
aesCipher.init( true, ivParams );
int remainingCount = length-pos;
if( remainingCount>=blockSize ) {
aesCipher.processBlock( in, pos, encryptedIn, 0 );
System.arraycopy( encryptedIn, 0, in, pos, blockSize );
mac.update( encryptedIn, 0, blockSize );
} else {
byte[] extendedIn = new byte[blockSize];
System.arraycopy( in, pos, extendedIn, 0, remainingCount );
aesCipher.processBlock( extendedIn, 0, encryptedIn, 0 );
System.arraycopy( encryptedIn, 0, in, pos, remainingCount );
mac.update( encryptedIn, 0, remainingCount );
}
}
/** 16 bytes (AES-256) set in constructor */
public byte[] getSalt() {
return salt;
}
/** 2 bytes for password verification set in constructor */
public byte[] getPwVerification() {
return pwVerification;
}
/** 10 bytes */
public byte[] getFinalAuthentication() {
// MAC / based on encIn + PASSWORD + SALT (encryption was successful)
byte[] macBytes = new byte[ mac.getMacSize() ];
mac.doFinal( macBytes, 0 );
byte[] macBytes10 = new byte[10];
System.arraycopy( macBytes, 0, macBytes10, 0, 10 );
return macBytes10;
}
// --------------------------------------------------------------------------
/**
* create 16 bytes salt by using each 4 bytes of 2 random 32 bit numbers
*/
protected static byte[] createSalt() {
byte[] salt = new byte[16];
for( int j=0; j<2; j++ ) {
Random rand = new Random();
int i = rand.nextInt();
salt[0+j*4] = (byte)(i>>24);
salt[1+j*4] = (byte)(i>>16);
salt[2+j*4] = (byte)(i>>8);
salt[3+j*4] = (byte)i;
}
return salt;
}
}
import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.Date;
import java.util.zip.ZipEntry;
public class AesZipEntry extends ZipEntry
{
public AesZipEntry( ZipEntry zipEntry ) throws Exception {
super(zipEntry.getName());
super.setMethod( zipEntry.getMethod() );
super.setSize( zipEntry.getSize() );
super.setCompressedSize( zipEntry.getCompressedSize() + 28 );
super.setTime( zipEntry.getTime() );
flag |= 1; // bit0 - encrypted
//flag |= 8; // bit3 - use data descriptor
}
public boolean useDataDescriptor() {
return ((flag & 8) == 8);
}
protected int flag;
public int getFlag() {
return this.flag;
}
protected int offset;
public int getOffset() {
return offset;
}
public void setOffset( int offset ) {
this.offset = offset;
}
// --------------------------------------------------------------------------
public long getDosTime() {
return javaToDosTime( getTime() );
}
protected static long javaToDosTime(long time) {
Date d = new Date(time);
Calendar ca = Calendar.getInstance();
ca.setTime( d );
int year = ca.get( Calendar.YEAR );
if (year < 1980) {
return (1 << 21) | (1 << 16);
}
return (year - 1980) << 25
| (ca.get(Calendar.MONTH) + 1) << 21
| ca.get(Calendar.DAY_OF_MONTH) << 16
| ca.get(Calendar.HOUR_OF_DAY) << 11
| ca.get(Calendar.MINUTE) << 5
| ca.get(Calendar.SECOND) >> 1;
}
}
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
public class AesZipOutputStream implements ZipConstants
{
protected OutputStream out;
protected AesZipOutputStream(File file) throws IOException {
out = new FileOutputStream(file);
}
protected AesZipOutputStream(OutputStream out) {
this.out = out;
}
protected void add( ZipFile inFile, String password ) throws IOException, UnsupportedEncodingException {
ZipFileEntryInputStream zfe = new ZipFileEntryInputStream( inFile );
Enumeration en = inFile.entries();
while( en.hasMoreElements() ) {
ZipEntry ze = (ZipEntry)en.nextElement();
zfe.nextEntry(ze);
add( ze, zfe, password );
}
zfe.close();
}
protected void add( ZipEntry zipEntry, ZipFileEntryInputStream zipData, String password ) throws IOException, UnsupportedEncodingException {
AESEncrypter aesEncrypter = new AESEncrypter(password.getBytes("iso-8859-1") );
AesZipEntry entry = null;
try {
entry = new AesZipEntry( zipEntry );
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
putNextEntry( entry );
/* ZIP-file data contains: 1. salt 2. pwVerification 3. ecnryptedContent 4. authenticationCode */
writeBytes( aesEncrypter.getSalt() );
writeBytes( aesEncrypter.getPwVerification() );
byte[] data = new byte[1024];
int read = zipData.read(data);
while( read!=-1 ) {
aesEncrypter.encrypt( data, read );
writeBytes( data, 0, read );
read = zipData.read(data);
}
writeBytes( aesEncrypter.getFinalAuthentication() );
}
protected void putNextEntry(AesZipEntry entry) throws IOException {
entries.add( entry );
entry.setOffset( written );
// file header signature
writeInt( LOCSIG );
writeFileInfo( entry );
writeBytes( entry.getName().getBytes("iso-8859-1") );
writeExtraBytes( entry );
}
private List entries = new ArrayList();
private final static short ZIP_VERSION = 20; // version set by java.util.zip
private void writeDirEntry( AesZipEntry entry ) throws IOException {
writeInt( CENSIG ); //writeBytes( new byte[] { 0x50, 0x4b, 0x01, 0x02 } ); // directory signature
writeShort( ZIP_VERSION ); // version made by
writeFileInfo(entry);
writeShort( 0x00 ); // file comment length 2 bytes
writeShort( 0x00 ); // disk number start (unused) 2 bytes
writeShort( 0x00 ); // internal file attributes (unsued) 2 bytes
writeInt( 0x00 ); // external file attributes (unused) 4 bytes
writeInt( entry.getOffset() ); // relative offset of local header 4 bytes
writeBytes( entry.getName().getBytes("iso-8859-1") );
writeExtraBytes( entry );
}
private void writeFileInfo( AesZipEntry entry ) throws IOException {
writeShort( ZIP_VERSION ); // version needed to extract
// general purpose bit flag - 0x0001 indicates encryption 2 bytes
writeShort( entry.getFlag() );
writeShort( 0x63 ); // primary compression method - 0x63==encryption
writeInt( entry.getDosTime() );
/*
writeBytes( new byte[] { (byte)0x5b, (byte)0x65 } ); // last mod file time
writeBytes( new byte[] { (byte)0x2d, (byte)0x35 } ); // last mod file date
*/
writeInt( 0x00 ); // CRC-32 / for encrypted files it's 0 as AES/MAC checks integritiy
// 28 bytes is the encryption overhead (caused by 256-bit AES key)
// 2 bytes pwVerification + 16 bytes SALT + 10 bytes AUTHENTICATION
writeInt( (int)entry.getCompressedSize() ); // compressed size
writeInt( (int)entry.getSize() ); // uncompressed size
writeShort( entry.getName().length() ); // file name length
writeShort( 0x0b ); // extra field length
}
private void writeExtraBytes( ZipEntry entry ) throws IOException {
byte[] extraBytes = new byte[11];
extraBytes[0] = 0x01;
extraBytes[1] = (byte)0x99;
extraBytes[2] = 0x07; // data size
extraBytes[3] = 0x00; // data size
extraBytes[4] = 0x02; // version number
extraBytes[5] = 0x00; // version number
extraBytes[6] = 0x41; // vendor id
extraBytes[7] = 0x45; // vendor id
extraBytes[8] = 0x03; // AES encryption strength - 1=128, 2=192, 3=256
// 41 45 03
// actual compression method - 0x0000==stored (no compression) - 2 bytes
extraBytes[9] = (byte)(entry.getMethod() & 0xff);
extraBytes[10]= (byte)((entry.getMethod() & 0xff00) >> 8);
writeBytes( extraBytes );
}
/**
* Finishes writing the contents of the ZIP output stream without closing the
* underlying stream. Also closes the stream.
*/
protected void finish() throws IOException {
int dirOffset = written; // central directory (at end of zip file) starts here
int startOfCentralDirectory = written;
Iterator it = entries.iterator();
while( it.hasNext() ) {
AesZipEntry entry = (AesZipEntry)it.next();
writeDirEntry( entry );
}
int centralDirectorySize = written - startOfCentralDirectory;
writeInt( ENDSIG ); //writeBytes( new byte[] { 0x50, 0x4b, 0x05, 0x06 } ); // end of central dir signature 4 bytes (0x06054b50)
writeShort( 0x00 ); // number of this disk 2 bytes
writeShort( 0x00 ); // number of the disk with the start of the central directory 2 bytes
writeShort( entries.size() ); // total number of entries in central directory on this disk 2 bytes
writeShort( entries.size() ); // total number of entries in the central directory 2 bytes
writeInt( centralDirectorySize ); // size of the central directory 4 bytes
writeInt( dirOffset ); // offset of start of central directory with respect to the starting disk number 4 bytes
writeShort( 0x00 ); // .ZIP file comment length 2 bytes
out.close();
}
// --------------------------------------------------------------------------
/** number of bytes written to out */
protected int written;
protected void writeBytes(byte[] b ) throws IOException {
out.write(b);
written+=b.length;
}
protected void writeShort(int v) throws IOException {
out.write((v >>> 0) & 0xff);
out.write((v >>> 8) & 0xff);
written+=2;
}
protected void writeInt(long v) throws IOException {
out.write((int)((v >>> 0) & 0xff));
out.write((int)((v >>> 8) & 0xff));
out.write((int)((v >>> 16) & 0xff));
out.write((int)((v >>> 24) & 0xff));
written+=4;
}
protected void writeBytes(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
written+=len;
}
// --------------------------------------------------------------------------
/**
* Compress (zip) inFile stored in created outFile.
* If you need multiple files added to outFile use
* java's ZipOutStream directly.
*/
public static void zip( File inFile, File outFile) throws IOException {
FileInputStream fin = new FileInputStream(inFile);
FileOutputStream fout = new FileOutputStream(outFile);
ZipOutputStream zout = new ZipOutputStream(fout);
zout.putNextEntry( new ZipEntry( inFile.getName() ) );
byte[] buffer = new byte[1024];
int len;
while( (len=fin.read(buffer))> 0) {
zout.write(buffer, 0, len);
}
zout.closeEntry();
zout.close();
fin.close();
}
/**
* encrypt zip file contents - encrypted data has the same size as the
* compressed data, though the file size is increased by 26 bytes for
* salt and pw-verification bytes
*
* @param pathName path to file inclusing filename but NOT file extension (is always ".zip")
* @param password used to perform the encryption
* @throws IOException
*/
public static void encrypt( String pathName, String password ) throws IOException {
AesZipOutputStream zos = new AesZipOutputStream( new File(pathName+"AES.zip") );
ZipFile zipFile = new ZipFile(pathName+".zip");
zos.add( zipFile, password );
zos.finish();
zipFile.close();
}
public static void zipAndEcrypt( String pathName, String extName, String password ) throws IOException {
File inFile = new File(pathName + "." + extName);
File outFile = new File(pathName + ".zip");
zip( inFile, outFile );
encrypt( pathName, password );
}
}
public class ByteArrayHelper
{
public static int fromLEByteArray(byte[] in) {
int out = 0;
if( in.length==4 ) {
out = in[3] & 0xff;
out = out << 8;
out |= in[2] & 0xff;
out = out << 8;
}
out |= in[1] & 0xff;
out = out << 8;
out |= in[0] & 0xff;
return out;
}
public static byte[] toLEByteArray(int in) {
byte[] out = new byte[4];
out[0] = (byte)in;
out[1] = (byte)(in >> 8);
out[2] = (byte)(in >> 16);
out[3] = (byte)(in >> 24);
return out;
}
public static byte[] toLEByteArray(int in,int outSize) {
byte[] out = new byte[outSize];
byte[] intArray = toLEByteArray(in);
for( int i=0; i<intArray.length && i<outSize; i++ ) {
out[i] = intArray[i];
}
return out;
}
public static String toString( byte[] theByteArray ){
StringBuffer theResult = new StringBuffer();
for( int i=0; i<theByteArray.length; i++ ) {
theResult.append( Integer.toHexString(theByteArray[i]&0xff) ).append(' ');
}
return theResult.toString();
}
}
public interface ZipConstants
{
/*
* Header signatures
*/
static long LOCSIG = 0x04034b50L; // "PK/003/004"
static long EXTSIG = 0x08074b50L; // "PK/007/008"
static long CENSIG = 0x02014b50L; // "PK/001/002"
static long ENDSIG = 0x06054b50L; // "PK/005/006"
/*
* Header sizes in bytes (including signatures)
*/
static final int LOCHDR = 30; // LOC header size
static final int EXTHDR = 16; // EXT header size
static final int CENHDR = 46; // CEN header size
static final int ENDHDR = 22; // END header size
/*
* Local file (LOC) header field offsets
*/
static final int LOCVER = 4; // version needed to extract
static final int LOCFLG = 6; // general purpose bit flag
static final int LOCHOW = 8; // compression method
static final int LOCTIM = 10; // modification time
static final int LOCCRC = 14; // uncompressed file crc-32 value
static final int LOCSIZ = 18; // compressed size
static final int LOCLEN = 22; // uncompressed size
static final int LOCNAM = 26; // filename length
static final int LOCEXT = 28; // extra field length
/*
* Extra local (EXT) header field offsets
*/
static final int EXTCRC = 4; // uncompressed file crc-32 value
static final int EXTSIZ = 8; // compressed size
static final int EXTLEN = 12; // uncompressed size
/*
* Central directory (CEN) header field offsets
*/
static final int CENVEM = 4; // version made by
static final int CENVER = 6; // version needed to extract
static final int CENFLG = 8; // encrypt, decrypt flags
static final int CENHOW = 10; // compression method
static final int CENTIM = 12; // modification time
static final int CENCRC = 16; // uncompressed file crc-32 value
static final int CENSIZ = 20; // compressed size
static final int CENLEN = 24; // uncompressed size
static final int CENNAM = 28; // filename length
static final int CENEXT = 30; // extra field length
static final int CENCOM = 32; // comment length
static final int CENDSK = 34; // disk number start
static final int CENATT = 36; // internal file attributes
static final int CENATX = 38; // external file attributes
static final int CENOFF = 42; // LOC header offset
/*
* End of central directory (END) header field offsets
*/
static final int ENDSUB = 8; // number of entries on this disk
static final int ENDTOT = 10; // total number of entries
static final int ENDSIZ = 12; // central directory size in bytes
static final int ENDOFF = 16; // offset of first CEN header
static final int ENDCOM = 20; // zip file comment length
}
import java.io.FileInputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
class ZipFileEntryInputStream extends FileInputStream implements ZipConstants {
protected long startPos;
protected long endPos;
protected long currentPos;
protected long compressedSize;
//Setting default to true - since if flag is not set - there is nothing to handle => already handled.
protected boolean dataDescriptorHandled = true;
public long getCompressedSize() {
return this.compressedSize;
}
ZipFileEntryInputStream( ZipFile zf ) throws IOException {
super( zf.getName() );
}
/**
* position input stream to start of ZipEntry this instance was created for
*
* @throws IOException
*/
protected void nextEntry( ZipEntry ze ) throws IOException {
this.compressedSize = ze.getCompressedSize();
//super.skip( 26 ); // 18 + compressedSize (4) + size (4)
super.skip( 6 );
byte[] generalPurposeFlagsBytes = new byte[2];
super.read( generalPurposeFlagsBytes );
//Check if bit 3 is set - note array-pos 1 is used since LE
//If it is set - we need to extend by 16 at end of file - check read method
if( ( generalPurposeFlagsBytes[0] | 0x08 ) == generalPurposeFlagsBytes[0] ) {
this.dataDescriptorHandled = false;
}
super.skip( 18 ); // 6 + 2 + ( 10 + compressedSize (4) + size (4) )
byte[] shortBuffer = new byte[2];
super.read( shortBuffer );
int fileNameLength = ByteArrayHelper.fromLEByteArray( shortBuffer );
super.read( shortBuffer );
int extraFieldLength = ByteArrayHelper.fromLEByteArray( shortBuffer );
startPos = 18 + 12 + fileNameLength + extraFieldLength;
currentPos = startPos;
endPos = startPos + this.compressedSize;
skip( fileNameLength + extraFieldLength );
}
// should work without this, but never trust an OO system
public int read(byte[] b) throws IOException {
return this.read(b,0,b.length);
}
public int read(byte[] b, int off, int len) throws IOException {
int bytesRead = -1;
int remainingBytes = (int)(endPos-currentPos);
if( remainingBytes>0 ) {
if( currentPos+len<endPos ) {
bytesRead = super.read(b, off, len);
currentPos += bytesRead;
} else {
bytesRead = super.read(b, off, remainingBytes );
currentPos += bytesRead;
if( ! this.dataDescriptorHandled ) {
super.skip( 16 );
this.dataDescriptorHandled = true;
}
}
} else {
if( ! this.dataDescriptorHandled ) {
super.skip( 16 );
this.dataDescriptorHandled = true;
}
}
return bytesRead;
}
}