All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dorkbox.util.crypto.Crypto Maven / Gradle / Ivy

There is a newer version: 1.48
Show newest version
/*
 * Copyright 2010 dorkbox, llc
 *
 * 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 dorkbox.util.crypto;


import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javax.crypto.Cipher;

import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.PBEParametersGenerator;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.lwjgl.util.xxhash.XXH32State;
import org.lwjgl.util.xxhash.XXHash;
import org.slf4j.Logger;

/**
 * http://en.wikipedia.org/wiki/NSA_Suite_B http://www.nsa.gov/ia/programs/suiteb_cryptography/
 * 

* NSA Suite B *

* TOP-SECRET LEVEL AES256/GCM ECC with 384-bit prime curve (FIPS PUB 186-3), and SHA-384 *

* SECRET LEVEL AES 128 ECDH and ECDSA using the 256-bit prime (FIPS PUB 186-3), and SHA-256. RSA with 2048 can be used for DH key * negotiation *

* WARNING! Note that this call is INCOMPATIBLE with GWT, so we have EXCLUDED IT from gwt, and created a CryptoGwt class in the web-client * project which only has the necessary crypto utility methods that are 1) Necessary 2) Compatible with GWT *

*

* To determine if we have hardware accelerated AES java -XX:+PrintFlagsFinal -version | grep UseAES * * Per NIST SP800-38D, * The total number of invocations of the authenticated encryption function shall not exceed 232, including all IV lengths and all instances of the authenticated encryption function with the given key. * */ public final class Crypto { private Crypto() { } // CUSTOM_HEADER USE // check to see if our extra data is OURS. if so, process it // cafeʞ, as UN signed bytes is: [254, 202, 202, 158], or as hex: FECA CA9E // cafeʞ, as signed bytes is: [-2, -54, -54, -98] private static final byte[] CUSTOM_HEADER = new byte[] {(byte) -2, (byte) -54, (byte) -54, (byte) -98}; public static void addProvider() { // make sure we only add it once (in case it's added elsewhere...) Provider provider = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME); if (provider == null) { Security.addProvider(new BouncyCastleProvider()); } } /** * Determines if cryptography restrictions apply. * Restrictions apply if the value of {@link Cipher#getMaxAllowedKeyLength(String)} returns a value smaller than {@link Integer#MAX_VALUE} if there are any restrictions according to the JavaDoc of the method. * This method is used with the transform "AES/CBC/PKCS5Padding" as this is an often used algorithm that is an implementation requirement for Java SE. * * @return true if restrictions apply, false otherwise */ public static boolean restrictedCryptography() { try { return Cipher.getMaxAllowedKeyLength("AES/CBC/PKCS5Padding") < Integer.MAX_VALUE; } catch (final NoSuchAlgorithmException e) { throw new IllegalStateException("The transform \"AES/CBC/PKCS5Padding\" is not available (the availability of this algorithm is mandatory for Java SE implementations)", e); } } public static byte[] hashFileMD5(File file) { MD5Digest digest = new MD5Digest(); return hashFile(file, digest, null); } public static byte[] hashFileSHA1(File file) { SHA1Digest digest = new SHA1Digest(); return hashFile(file, digest, null); } public static byte[] hashFileSHA256(File file) { SHA256Digest digest = new SHA256Digest(); return hashFile(file, digest, null); } public static byte[] hashFileSHA512(File file) { SHA512Digest digest = new SHA512Digest(); return hashFile(file, digest, null); } /** * Return the hash of the file or NULL if file is invalid * * @param logger * may be null, if no log output is necessary */ public static byte[] hashFile(File file, Digest digest, Logger logger) { return hashFile(file, digest, 0L, file.length(), logger); } /** * Return the hash of the file or NULL if file is invalid * * @param logger * may be null, if no log output is necessary */ public static byte[] hashFile(File file, Digest digest, long startPosition, long endPosition, Logger logger) { if (file.isFile() && file.canRead()) { InputStream inputStream = null; try { inputStream = new FileInputStream(file); long skip = inputStream.skip(startPosition); if (skip != startPosition) { throw new RuntimeException("Unable to skip " + startPosition + " bytes. Only skippped " + skip + " instead"); } long size = file.length() - startPosition; long lengthFromEnd = size - endPosition; if (lengthFromEnd > 0 && lengthFromEnd < size) { size -= lengthFromEnd; } int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int readBytes; digest.reset(); while (size > 0) { //noinspection NumericCastThatLosesPrecision int maxToRead = (int) Math.min(bufferSize, size); readBytes = inputStream.read(buffer, 0, maxToRead); size -= readBytes; if (readBytes == 0) { //wtf. finally still gets called. return null; } digest.update(buffer, 0, readBytes); } } catch (Exception e) { if (logger != null) { logger.error("Error hashing file: {}", file.getAbsolutePath(), e); } else { e.printStackTrace(); } } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } byte[] digestBytes = new byte[digest.getDigestSize()]; digest.doFinal(digestBytes, 0); return digestBytes; } else { return null; } } /** * Return the xxhash of the file as or 0 if file is invalid * * @param logger * may be null, if no log output is necessary */ public static int xxHashFile(File file, long lengthFromEnd, Logger logger) { if (file.isFile() && file.canRead()) { InputStream inputStream = null; // used to initialize the hash value, use whatever value you want, but always the same int seed = 0x9747b28c; // must match number in C (in Auth::xxHash32()) XXH32State state = XXHash.XXH32_createState(); XXHash.XXH32_reset(state, seed); try { inputStream = new FileInputStream(file); long size = file.length(); if (lengthFromEnd > 0 && lengthFromEnd < size) { size -= lengthFromEnd; } int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; ByteBuffer bbuffer = ByteBuffer.wrap(buffer); int readBytes; while (size > 0) { //noinspection NumericCastThatLosesPrecision int maxToRead = (int) Math.min(bufferSize, size); readBytes = inputStream.read(buffer, 0, maxToRead); size -= readBytes; if (readBytes == 0) { //wtf. finally still gets called. return 0; } bbuffer.limit(readBytes); XXHash.XXH32_update(state, bbuffer); } } catch (Exception e) { if (logger != null) { logger.error("Error hashing file: {}", file.getAbsolutePath(), e); } else { e.printStackTrace(); } } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } return XXHash.XXH32_digest(state); } else { return 0; } } static int toInt(final byte[] bytes) { int number = 0; switch (bytes.length) { default: case 4: number |= (bytes[3] & 0xFF) << 24; case 3: number |= (bytes[2] & 0xFF) << 16; case 2: number |= (bytes[1] & 0xFF) << 8; case 1: number |= (bytes[0] & 0xFF) << 0; } return number; } /** * Specifically, to return the hash of the ALL files/directories inside the jar, minus the action specified (LGPL) files. */ public static byte[] hashJarContentsExcludeAction(File jarDestFilename, Digest digest, int action) throws IOException { JarFile jarDestFile = new JarFile(jarDestFilename); try { Enumeration jarElements = jarDestFile.entries(); boolean okToHash; boolean hasAction; byte[] buffer = new byte[2048]; int read; digest.reset(); while (jarElements.hasMoreElements()) { JarEntry jarEntry = jarElements.nextElement(); String name = jarEntry.getName(); okToHash = !jarEntry.isDirectory(); if (!okToHash) { continue; } // data with NO extra data will NOT BE HASHED // data that matches our action bitmask WILL NOT BE HASHED okToHash = false; hasAction = false; byte[] extraData = jarEntry.getExtra(); if (extraData == null || extraData.length == 0) { okToHash = false; } else if (extraData.length >= 4) { for (int i = 0; i < CUSTOM_HEADER.length; i++) { if (extraData[i] != CUSTOM_HEADER[i]) { throw new RuntimeException("Unexpected extra data in zip assigned. Aborting"); } } // this means we matched our header if (extraData[4] > 0) { hasAction = true; // we have an ACTION describing how it was compressed, etc int fileAction = toInt(new byte[] {extraData[5], extraData[6], extraData[7], extraData[8]}); if ((fileAction & action) != action) { okToHash = true; } } else { okToHash = true; } } else { throw new RuntimeException("Unexpected extra data in zip assigned. Aborting"); } // skips hashing lgpl files. (technically, whatever our action bitmask is...) // we want to hash everything BY DEFAULT. we ALSO want to hash the NAME, LOAD ACTION TYPE, and the contents if (okToHash) { // System.err.println("HASHING: " + name); // hash the file name byte[] bytes = name.getBytes(StandardCharsets.US_ASCII); digest.update(bytes, 0, bytes.length); if (hasAction) { // hash the action - since we don't want to permit anyone to change this after we sign the file digest.update(extraData, 5, 4); } // hash the contents InputStream inputStream = jarDestFile.getInputStream(jarEntry); while ((read = inputStream.read(buffer)) > 0) { digest.update(buffer, 0, read); } inputStream.close(); } //else { // System.err.println("Skipping: " + name); //} } } catch (Exception e) { throw new RuntimeException("Unexpected extra data in zip assigned. Aborting"); } finally { jarDestFile.close(); } byte[] digestBytes = new byte[digest.getDigestSize()]; digest.doFinal(digestBytes, 0); return digestBytes; } /** * Hash an input stream, based on the specified digest */ public static byte[] hashStream(Digest digest, InputStream inputStream) throws IOException { byte[] buffer = new byte[2048]; int read; digest.reset(); while ((read = inputStream.read(buffer)) > 0) { digest.update(buffer, 0, read); } inputStream.close(); byte[] digestBytes = new byte[digest.getDigestSize()]; digest.doFinal(digestBytes, 0); return digestBytes; } /** * Secure way to generate an AES key based on a password. Will '*' out the passed-in password * * @param password * will be filled with '*' * @param salt * should be a RANDOM number, at least 256bits (32 bytes) in size. * @param iterationCount * should be a lot, like 10,000 * * @return the secure key to use */ public static byte[] PBKDF2(char[] password, byte[] salt, int iterationCount) { // will also zero out the password. byte[] charToBytes = Crypto.charToBytesPassword_UTF16(password); return PBKDF2(charToBytes, salt, iterationCount); } /** * Secure way to generate an AES key based on a password. * * @param password * The password that you want to mix * @param salt * should be a RANDOM number, at least 256bits (32 bytes) in size. * @param iterationCount * should be a lot, like 10,000 * * @return the secure key to use */ public static byte[] PBKDF2(byte[] password, byte[] salt, int iterationCount) { SHA256Digest digest = new SHA256Digest(); PBEParametersGenerator pGen = new PKCS5S2ParametersGenerator(digest); pGen.init(password, salt, iterationCount); KeyParameter key = (KeyParameter) pGen.generateDerivedMacParameters(digest.getDigestSize() * 8); // *8 for bit length. // zero out the password. Arrays.fill(password, (byte) 0); return key.getKey(); } /** * this saves the char array in UTF-16 format of bytes and BLANKS out the password char array. */ public static byte[] charToBytesPassword_UTF16(char[] password) { // note: this saves the char array in UTF-16 format of bytes. byte[] passwordBytes = new byte[password.length * 2]; for (int i = 0; i < password.length; i++) { //noinspection NumericCastThatLosesPrecision passwordBytes[2 * i] = (byte) (((int) password[i] & 0xFF00) >> 8); //noinspection NumericCastThatLosesPrecision passwordBytes[2 * i + 1] = (byte) ((int) password[i] & 0x00FF); } // asterisk out the password Arrays.fill(password, '*'); return passwordBytes; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy