me.footlights.core.crypto.SecretKey.java Source code

Java tutorial

Introduction

Here is the source code for me.footlights.core.crypto.SecretKey.java

Source

/*
 * Copyright 2011 Jonathan Anderson
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package me.footlights.core.crypto;

import java.net.URI;
import java.net.URISyntaxException;

import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Hex;

import me.footlights.core.Preferences;

/** A secret, symmetric key. */
public class SecretKey {
    public enum Operation {
        ENCRYPT(Cipher.ENCRYPT_MODE), DECRYPT(Cipher.DECRYPT_MODE);

        private Operation(int value) {
            this.value = value;
        }

        int opcode() {
            return value;
        }

        private final int value;
    };

    public String getAlgorithm() {
        return keySpec.getAlgorithm();
    }

    public Fingerprint getFingerprint() {
        return fingerprint;
    }

    public SecretKeySpec getKey() {
        return keySpec;
    }

    public URI toUri() {
        return uri;
    }

    /** Parse a hexadecimal URI. */
    public static SecretKey parse(URI uri)
            throws GeneralSecurityException, org.apache.commons.codec.DecoderException {
        return newGenerator().setAlgorithm(uri.getScheme())
                .setBytes(Hex.decodeHex(uri.getSchemeSpecificPart().toCharArray())).generate();
    }

    /** Generate a new secret key. */
    public static Generator newGenerator() {
        return new Generator();
    }

    /** Get the cipher instance, which can be used for encrypting and decrypting data. */
    public CipherBuilder newCipherBuilder() {
        return new CipherBuilder();
    }

    /** Start constructing a {@link Link} to data encrypted by this key. */
    Link.Builder createLinkBuilder() {
        return Link.newBuilder().setFingerprint(fingerprint).setKey(this);
    }

    public static class Generator {
        public String getAlgorithm() {
            return algorithm;
        }

        public int getKeyLength() {
            return keylen;
        }

        public Generator setAlgorithm(String a) {
            algorithm = a;
            return this;
        }

        public Generator setBytes(byte[] s) {
            secret = s;
            return this;
        }

        public Generator setFingerprintAlgorithm(String a) throws NoSuchAlgorithmException {
            fingerprint.setAlgorithm(a);
            return this;
        }

        public Generator setKey(SecretKeySpec spec) {
            keySpec = spec;
            return this;
        }

        public Generator setKeyLength(int l) {
            keylen = l;
            return this;
        }

        public SecretKey generate() throws GeneralSecurityException {
            if (algorithm.contains("/"))
                throw new NoSuchAlgorithmException(
                        "Don't need mode or padding information in " + SecretKey.class.getName());

            if (secret == null) {
                secret = new byte[keylen];
                SecureRandom.getInstance(preferences.getString("crypto.prng").get()).nextBytes(secret);
            }

            if (keySpec == null)
                keySpec = new SecretKeySpec(secret, algorithm);

            return new SecretKey(keySpec, fingerprint.setContent(secret).build());
        }

        private String algorithm = preferences.getString("crypto.sym.algorithm").get();

        private int keylen = preferences.getInt("crypto.sym.keylen").get();
        private byte[] secret = null;
        private Fingerprint.Builder fingerprint = Fingerprint.newBuilder();
        private SecretKeySpec keySpec;
    }

    public class CipherBuilder {
        public Cipher build() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
                InvalidAlgorithmParameterException {
            String fullAlgorithm = keySpec.getAlgorithm() + "/" + mode + "/" + padding;
            Cipher cipher = Cipher.getInstance(fullAlgorithm);

            IvParameterSpec iv = null;
            if (!mode.equals("ECB"))
                iv = new IvParameterSpec(new byte[cipher.getBlockSize()]);

            cipher.init(operation.opcode(), keySpec, iv);

            return cipher;
        }

        public CipherBuilder parseAlgorithm(String a) {
            String[] parts = a.split("/");
            if (parts.length != 3)
                throw new IllegalArgumentException(
                        "parseAlgorithm() requires 'alg/mode/padding' string, got '" + a + "'");

            if (!parts[0].equals(keySpec.getAlgorithm()))
                throw new IllegalArgumentException(
                        "Wrong algorithm: got " + parts[0] + ", expected " + keySpec.getAlgorithm());

            setMode(parts[1]);
            setPaddingScheme(parts[2]);

            return this;
        }

        public CipherBuilder setOperation(Operation o) {
            operation = o;
            return this;
        }

        public CipherBuilder setMode(String m) {
            mode = m;
            return this;
        }

        public CipherBuilder setPaddingScheme(String p) {
            padding = p;
            return this;
        }

        private CipherBuilder() {
        }

        private Operation operation = Operation.ENCRYPT;
        private String mode = preferences.getString("crypto.sym.mode").get();
        private String padding = preferences.getString("crypto.sym.padding").get();
    }

    // Object override.
    @Override
    public String toString() {
        return SecretKey.class.getSimpleName() + " { " + fingerprint + " }";
    }

    @Override
    public boolean equals(Object other) {
        if (!(other instanceof SecretKey))
            return false;
        SecretKey o = (SecretKey) other;

        return (this.uri.equals(o.uri));
    }

    private SecretKey(SecretKeySpec key, Fingerprint fingerprint) {
        this.keySpec = key;
        this.fingerprint = fingerprint;

        String algorithm = keySpec.getAlgorithm();
        String keyData = new String(Hex.encodeHex(keySpec.getEncoded()));

        try {
            this.uri = new URI(algorithm, keyData, null);
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException("Unable to generate symmetric key URI " + algorithm + ":" + keyData,
                    e);
        }
    }

    final SecretKeySpec keySpec;
    private final Fingerprint fingerprint;
    private final URI uri;

    /** Footlights-wide preferences. */
    private static Preferences preferences = Preferences.getDefaultPreferences();
}