Posts Tagged ‘Password’

Library for Reading and Writing Encrypted Zip Files in Java

March 31st, 2012

More than two years ago I posted this blog entry, where I showed a little utility for reading password-protected zip files in Java. Since then it seems it helped a lot of people and many asked if I could provide something for the other direction – i.e. writing encrypted files. Finally I’ve decided to add that as well as improve the original class for reading and recently I published a small open source project which provides both the ZipDecryptInputStream (for reading) as well as ZipEncryptOutputStream (for writing). The project is named ziputils and is available here: https://bitbucket.org/matulic/ziputils/overview.

The usage is very simple. ZipDecryptInputStream can be used in the same way as outlined in my old blog entry. ZipEncryptOutputStream which I added can be used in a very similar way:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

// usage: java Main zip-file-name password filename1 [filename2 [filename3 [...]]]
public class Main {
    public static void main(String[] args) throws IOException {
        // create a stream that will encrypt the resulting zip file
        // using the password provided in the second command line argument
        ZipEncryptOutputStream zeos = new ZipEncryptOutputStream(new FileOutputStream(args[0]), args[1]);
        // create the standard zip output stream, initialize it with our encrypting stream
        ZipOutputStream zos = new ZipOutputStream(zeos);

        // write the zip file
        for (int i = 2; i < args.length; i++) {
            ZipEntry ze = new ZipEntry(args[i]);
            zos.putNextEntry(ze);
            InputStream is = new FileInputStream(args[i]);
            int b;
            while ((b = is.read()) != -1) {
                zos.write(b);
            }
            zos.closeEntry();
        }
        zos.close();
    }
}

You can find more about how to use the classes in the project javadoc. To download the ziputils jar, go to the project downloads page. Enjoy!

Reading Password-Protected ZIP Files in Java

October 31st, 2009

On a recent “fun” project, I needed my application to be able to access password-protected zip files of a particular format. It was one of these features I thought will take me no time to implement. Anyway, to my surprise, neither JDK supports password-protected ZIP files, nor I was able to find a suitable Java open source library I could use for that purpose. So, I ended up writing the utility class on my own. I wrote an implementation of java.io.InputStream that filters the ZIP file data and turns a password-protected ZIP into an unprotected one on the fly – so the stream can be nicely chained with java.util.zip.ZipInputStream. Although the class is specifically targeted at the particular type of ZIP files I had to deal with (see the limitations below), maybe other people have to deal with the same type of files, or this class can provide a good start for others to turn it into a utility that would work with any type of ZIP (maybe I will do it myself some day – for now I don’t have time).
To implement this class I used the ZIP File Format Specification as the source of information. I also used the 7-zip project (C++) as a reference during the debugging to verify my understanding of the ZIP spec. and the CRC algorithm.
So, here is the class:

import java.io.IOException;
import java.io.InputStream;

public class ZipDecryptInputStream extends InputStream {
    private static final int[] CRC_TABLE = new int[256];
    // compute the table
    // (could also have it pre-computed - see http://snippets.dzone.com/tag/crc32)
    static {
        for (int i = 0; i < 256; i++) {
            int r = i;
            for (int j = 0; j < 8; j++) {
                if ((r & 1) == 1) {
                    r = (r >>> 1) ^ 0xedb88320;
                } else {
                    r >>>= 1;
                }
            }
            CRC_TABLE[i] = r;
        }
    }

    private static final int DECRYPT_HEADER_SIZE = 12;
    private static final int[] LFH_SIGNATURE = {0x50, 0x4b, 0x03, 0x04};

    private final InputStream delegate;
    private final String password;
    private final int keys[] = new int[3];

    private State state = State.SIGNATURE;
    private int skipBytes;
    private int compressedSize;
    private int value;
    private int valuePos;
    private int valueInc;

    public ZipDecryptInputStream(InputStream stream, String password) {
        this.delegate = stream;
        this.password = password;
    }

    @Override
    public int read() throws IOException {
        int result = delegate.read();
        if (skipBytes == 0) {
            switch (state) {
                case SIGNATURE:
                    if (result != LFH_SIGNATURE[valuePos]) {
                        state = State.TAIL;
                    } else {
                        valuePos++;
                        if (valuePos >= LFH_SIGNATURE.length) {
                            skipBytes = 2;
                            state = State.FLAGS;
                        }
                    }
                    break;
                case FLAGS:
                    if ((result & 1) == 0) {
                        throw new IllegalStateException("ZIP not password protected.");
                    }
                    if ((result & 64) == 64) {
                        throw new IllegalStateException("Strong encryption used.");
                    }
                    if ((result & 8) == 8) {
                        throw new IllegalStateException("Unsupported ZIP format.");
                    }
                    result -= 1;
                    compressedSize = 0;
                    valuePos = 0;
                    valueInc = DECRYPT_HEADER_SIZE;
                    state = State.COMPRESSED_SIZE;
                    skipBytes = 11;
                    break;
                case COMPRESSED_SIZE:
                    compressedSize += result << (8 * valuePos);
                    result -= valueInc;
                    if (result < 0) {
                        valueInc = 1;
                        result += 256;
                    } else {
                        valueInc = 0;
                    }
                    valuePos++;
                    if (valuePos > 3) {
                        valuePos = 0;
                        value = 0;
                        state = State.FN_LENGTH;
                        skipBytes = 4;
                    }
                    break;
                case FN_LENGTH:
                case EF_LENGTH:
                    value += result << 8 * valuePos;
                    if (valuePos == 1) {
                        valuePos = 0;
                        if (state == State.FN_LENGTH) {
                            state = State.EF_LENGTH;
                        } else {
                            state = State.HEADER;
                            skipBytes = value;
                        }
                    } else {
                        valuePos = 1;
                    }
                    break;
                case HEADER:
                    initKeys(password);
                    for (int i = 0; i < DECRYPT_HEADER_SIZE; i++) {
                        updateKeys((byte) (result ^ decryptByte()));
                        result = delegate.read();
                    }
                    compressedSize -= DECRYPT_HEADER_SIZE;
                    state = State.DATA;
                    // intentionally no break
                case DATA:
                    result = (result ^ decryptByte()) & 0xff;
                    updateKeys((byte) result);
                    compressedSize--;
                    if (compressedSize == 0) {
                        valuePos = 0;
                        state = State.SIGNATURE;
                    }
                    break;
                case TAIL:
                    // do nothing
            }
        } else {
            skipBytes--;
        }
        return result;
    }

    @Override
    public void close() throws IOException {
        delegate.close();
        super.close();
    }

    private void initKeys(String password) {
        keys[0] = 305419896;
        keys[1] = 591751049;
        keys[2] = 878082192;
        for (int i = 0; i < password.length(); i++) {
            updateKeys((byte) (password.charAt(i) & 0xff));
        }
    }

    private void updateKeys(byte charAt) {
        keys[0] = crc32(keys[0], charAt);
        keys[1] += keys[0] & 0xff;
        keys[1] = keys[1] * 134775813 + 1;
        keys[2] = crc32(keys[2], (byte) (keys[1] >> 24));
    }

    private byte decryptByte() {
        int temp = keys[2] | 2;
        return (byte) ((temp * (temp ^ 1)) >>> 8);
    }

    private int crc32(int oldCrc, byte charAt) {
        return ((oldCrc >>> 8) ^ CRC_TABLE[(oldCrc ^ charAt) & 0xff]);
    }

    private static enum State {
        SIGNATURE, FLAGS, COMPRESSED_SIZE, FN_LENGTH, EF_LENGTH, HEADER, DATA, TAIL
    }
}

These are the limitations:

  • Only the “Traditional PKWARE Encryption” is supported (spec. section VII)
  • Files that have the “compressed length” information at the end of the data section (rather than at the beginning) are not supported (see “general purpose bit flag”, bit 3 in section V, subsection J in the spec.)

And this is how you can use it in your code:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

// usage: java Main [filename] [password]
public class Main {
    public static void main(String[] args) throws IOException {
        // password-protected zip file I need to read
        FileInputStream fis = new FileInputStream(args[0]);
        // wrap it in the decrypt stream
        ZipDecryptInputStream zdis = new ZipDecryptInputStream(fis, args[1]);
        // wrap the decrypt stream by the ZIP input stream
        ZipInputStream zis = new ZipInputStream(zdis);

        // read all the zip entries and save them as files
        ZipEntry ze;
        while ((ze = zis.getNextEntry()) != null) {
            FileOutputStream fos = new FileOutputStream(ze.getName());
            int b;
            while ((b = zis.read()) != -1) {
                fos.write(b);
            }
            fos.close();
            zis.closeEntry();
        }
        zis.close();
    }
}

JavaFX Password Field

September 12th, 2009

As I mentioned in My First Experience with JavaFX blog, there is no password field in JavaFX, so I had to google for some workarounds. Although I found a few, none of them worked flawlessly, so last night I decided to spend some time trying to come up with a password field that would really work as expected. And, I think I managed to come up with an elegant and simple solution. No hacking in form of covering the text area with additional components or adding effects that blur the text box (including the caret and component borders). It looks and behaves exactly as you would expect of a password field. Click on the following picture to try it out:

Or click the following button for standalone mode:

And here is how it is implemented:

import javafx.scene.control.TextBox;
import javafx.util.Math;

/** * @author Martin Matula */
public class PasswordBox extends TextBox {
    public-read var password = "";

    override function replaceSelection(arg) {
        var pos1 = Math.min(dot, mark);
        var pos2 = Math.max(dot, mark);
        password = "{password.substring(0, pos1)}{arg}{password.substring(pos2)}";
        super.replaceSelection(getStars(arg.length()));
    }

    override function deleteNextChar() {
        if ((mark == dot) and (dot < password.length())) {
            password = "{password.substring(0, dot)}{password.substring(dot + 1)}";
        }
        super.deleteNextChar();
    }

    override function deletePreviousChar() {
        if ((mark == dot) and (dot > 0)) {
            password = "{password.substring(0, dot - 1)}{password.substring(dot)}";
        }
        super.deletePreviousChar();
    }

    function getStars(len: Integer): String {
        var result: String = "";
        for (i in [1..len]) {
            result = "{result}*";
        }
        result;
    }
}

Quite simple, isn’t it? Here is how it is used in the Main class:

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.text.Text;

var password: PasswordBox;

var stage: Stage = Stage {
    title: "PasswordBox Demo"
    width: 200
    height: 100
    scene: Scene {
        content: [
            password = PasswordBox {
                translateX: 10
                translateY: 10
                columns: 20
            },
            Text {
                x: 10
                y: 50
                content: bind password.password
            }
        ]
    }
}

I guess there is still a room for improving the API – the real password is stored in the password property, while the text property became useless and should never be set by a client. If you want to populate the field with a remembered password, you need to do it by calling replaceSelection("password") on the password field after it’s initialization (rather than setting the text or the password properties). Anyway, I wanted to keep the code simple so that you can easily see the basic idea behind it.