Package com.trsst

Source Code of com.trsst.Crypto

/*
* Copyright 2013 mpowers
*
* 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 com.trsst;

import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;

import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.engines.IESEngine;
import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.IESCipher;

/**
* Shared utilities to try to keep the cryptography implementation in one place
* for easier review.
*
* @author mpowers
*/
public class Crypto {

    public static byte[] encryptKeyWithIES(byte[] input, long entryId,
            PublicKey publicKey, PrivateKey privateKey)
            throws GeneralSecurityException {
        try {
            // BC appears to be happier with BCECPublicKeys:
            // see BC's IESCipher.engineInit's check for ECPublicKey
            publicKey = new BCECPublicKey((ECPublicKey) publicKey, null);

            return _cryptIES(input, publicKey, true);
        } catch (GeneralSecurityException e) {
            log.error("Error while encrypting key", e);
            throw e;
        }
    }

    public static byte[] decryptKeyWithIES(byte[] input, long entryId,
            PublicKey publicKey, PrivateKey privateKey)
            throws GeneralSecurityException {
        try {
            // BC appears to be happier with BCECPrivateKeys:
            privateKey = new BCECPrivateKey((ECPrivateKey) privateKey, null);

            return _cryptIES(input, privateKey, false);
        } catch (GeneralSecurityException e) {
            log.error("Error while decrypting key", e);
            throw new GeneralSecurityException(e);
        }
    }

    private static byte[] _cryptIES(byte[] input, Key recipient,
            boolean forEncryption) throws InvalidKeyException,
            IllegalBlockSizeException, BadPaddingException {
        IESCipher cipher = new IESCipher(new IESEngine(
                new ECDHBasicAgreement(), new KDF2BytesGenerator(
                        new SHA1Digest()), new HMac(new SHA256Digest()),
                new PaddedBufferedBlockCipher(new CBCBlockCipher(
                        new AESEngine()))));

        cipher.engineInit(forEncryption ? Cipher.ENCRYPT_MODE
                : Cipher.DECRYPT_MODE, recipient, new SecureRandom());
        return cipher.engineDoFinal(input, 0, input.length);
    }

    public static byte[] generateAESKey() {
        byte[] result = new byte[32];
        new SecureRandom().nextBytes(result);
        return result;
    }

    public static byte[] encryptAES(byte[] input, byte[] key)
            throws InvalidCipherTextException {
        return _cryptBytesAES(input, key, true);
    }

    public static byte[] decryptAES(byte[] input, byte[] key)
            throws InvalidCipherTextException {
        return _cryptBytesAES(input, key, false);
    }

    // h/t Steve Weis, Michael Rogers, and liberationtech
    private static byte[] _cryptBytesAES(byte[] input, byte[] key,
            boolean forEncryption) throws InvalidCipherTextException {
        assert key.length == 32; // 32 bytes == 256 bits
        return process(input, new PaddedBufferedBlockCipher(new CBCBlockCipher(
                new AESEngine())), new KeyParameter(key), forEncryption);
        // note: using zero IV because we generate a new key for every message
    }

    // h/t Adam Paynter http://stackoverflow.com/users/41619/
    private static byte[] process(byte[] input,
            BufferedBlockCipher bufferedBlockCipher,
            CipherParameters cipherParameters, boolean forEncryption)
            throws InvalidCipherTextException {
        bufferedBlockCipher.init(forEncryption, cipherParameters);

        int inputOffset = 0;
        int inputLength = input.length;

        int maximumOutputLength = bufferedBlockCipher
                .getOutputSize(inputLength);
        byte[] output = new byte[maximumOutputLength];
        int outputOffset = 0;
        int outputLength = 0;

        int bytesProcessed;

        bytesProcessed = bufferedBlockCipher.processBytes(input, inputOffset,
                inputLength, output, outputOffset);
        outputOffset += bytesProcessed;
        outputLength += bytesProcessed;

        bytesProcessed = bufferedBlockCipher.doFinal(output, outputOffset);
        outputOffset += bytesProcessed;
        outputLength += bytesProcessed;

        if (outputLength == output.length) {
            return output;
        } else {
            byte[] truncatedOutput = new byte[outputLength];
            System.arraycopy(output, 0, truncatedOutput, 0, outputLength);
            return truncatedOutput;
        }
    }

    /**
     * Computes hashcash proof-of-work stamp for the given input and
     * bitstrength. Servers can choose which bitstrength they accept, but we
     * recommend at least 20. The colon ":" is a delimiter in hashcash so we
     * replace all occurances in a token with ".".
     *
     * This machine is calculating stamps at a mean rate of 340ms, 694ms,
     * 1989ms, 4098ms, and 6563ms for bits of 19, 20, 21, 22, and 23
     * respectively.
     *
     * @param bitstrength
     *            number of leading zero bits to find
     * @param timestamp
     *            the timestamp/entry-id of the enclosing entry
     * @param token
     *            a feed-id or mention-id or tag
     * @return
     */
    public static final String computeStamp(int bitstrength, long timestamp,
            String token) {
        try {
            if (token.indexOf(':') != -1) {
                token = token.replace(":", ".");
            }
            String formattedDate = new SimpleDateFormat("YYMMdd")
                    .format(new Date(timestamp));
            String prefix = "1:" + Integer.toString(bitstrength) + ":"
                    + formattedDate + ":" + token + "::"
                    + Long.toHexString(timestamp) + ":";
            int masklength = bitstrength / 8;
            byte[] prefixBytes = prefix.getBytes("UTF-8");
            MessageDigest sha1 = MessageDigest.getInstance("SHA-1");

            int i;
            int b;
            byte[] hash;
            long counter = 0;
            while (true) {
                sha1.update(prefixBytes);
                sha1.update(Long.toHexString(counter).getBytes());
                hash = sha1.digest(); // 20 bytes long
                for (i = 0; i < 20; i++) {
                    b = (i < masklength) ? 0 : 255 >> (bitstrength % 8);
                    if (b != (b | hash[i])) {
                        // no match; keep trying
                        break;
                    }
                    if (i == masklength) {
                        // we're a match: return the stamp
                        // System.out.println(Common.toHex(hash));
                        return prefix + Long.toHexString(counter);
                    }
                }
                counter++;
                // keep going forever until we find it
            }
        } catch (UnsupportedEncodingException e) {
            log.error("No string encoding found: ", e);
        } catch (NoSuchAlgorithmException e) {
            log.error("No hash algorithm found: ", e);
        }
        log.error("Exiting without stamp: should never happen");
        return null;
    }

    /**
     * Verifies the specified hashcash proof-of-work stamp for the given
     * timestamp and token.
     *
     * @return true if verified, false if failed or invalid.
     */
    public static final boolean verifyStamp(String stamp, long timestamp,
            String token) {
        String[] fields = stamp.split(":");

        if (fields.length != 7) {
            log.info("verifyStamp: invalid number of fields: " + fields.length);
            return false;
        }

        if (!"1".equals(fields[0])) {
            log.info("verifyStamp: invalid version: " + fields[0]);
            return false;
        }

        int bitstrength;
        try {
            bitstrength = Integer.parseInt(fields[1]);
        } catch (NumberFormatException e) {
            log.info("verifyStamp: invalid bit strength: " + fields[1]);
            return false;
        }

        String formattedDate = new SimpleDateFormat("YYMMdd").format(new Date(
                timestamp));
        if (!formattedDate.equals(fields[2])) {
            log.info("verifyStamp: invalid date: " + fields[2]);
            return false;
        }

        if (!token.equals(fields[3])) {
            log.info("verifyStamp: invalid token: " + fields[3]);
            return false;
        }

        // other fields are ignored;
        // now verify hash:
        try {
            int b;
            byte[] hash = MessageDigest.getInstance("SHA-1").digest(
                    stamp.getBytes("UTF-8"));
            for (int i = 0; i < 20; i++) {
                b = (i < bitstrength / 8) ? 0 : 255 >> (bitstrength % 8);
                if (b != (b | hash[i])) {
                    return false;
                }
                if (i == bitstrength / 8) {
                    // stamp is verified
                    return true;
                }
            }
        } catch (UnsupportedEncodingException e) {
            log.error("No string encoding found: ", e);
        } catch (NoSuchAlgorithmException e) {
            log.error("No hash algorithm found: ", e);
        }

        return false;
    }

    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory
            .getLogger(Crypto.class);
}
TOP

Related Classes of com.trsst.Crypto

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.