Extracted out crypto classes. Added PGP handling
This commit is contained in:
parent
7388a8efb7
commit
88ccf24895
@ -1,3 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
package dorkbox.util.crypto;
|
||||||
|
|
||||||
// Copyright (c) 2006 Damien Miller <djm@mindrot.org>
|
// Copyright (c) 2006 Damien Miller <djm@mindrot.org>
|
||||||
|
@ -16,11 +16,14 @@
|
|||||||
package dorkbox.util.crypto;
|
package dorkbox.util.crypto;
|
||||||
|
|
||||||
|
|
||||||
|
import com.twmacinta.util.MD5;
|
||||||
import dorkbox.util.OS;
|
import dorkbox.util.OS;
|
||||||
import dorkbox.util.bytes.LittleEndian;
|
import dorkbox.util.bytes.LittleEndian;
|
||||||
import org.bouncycastle.crypto.Digest;
|
import org.bouncycastle.crypto.Digest;
|
||||||
import org.bouncycastle.crypto.PBEParametersGenerator;
|
import org.bouncycastle.crypto.PBEParametersGenerator;
|
||||||
|
import org.bouncycastle.crypto.digests.SHA1Digest;
|
||||||
import org.bouncycastle.crypto.digests.SHA256Digest;
|
import org.bouncycastle.crypto.digests.SHA256Digest;
|
||||||
|
import org.bouncycastle.crypto.digests.SHA512Digest;
|
||||||
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
|
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
|
||||||
import org.bouncycastle.crypto.params.KeyParameter;
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
@ -57,6 +60,15 @@ import java.util.jar.JarFile;
|
|||||||
public final
|
public final
|
||||||
class Crypto {
|
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
|
public static
|
||||||
void addProvider() {
|
void addProvider() {
|
||||||
@ -67,293 +79,308 @@ class Crypto {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private
|
|
||||||
Crypto() {
|
public static
|
||||||
|
byte[] hashFileMD5(File file) {
|
||||||
|
try {
|
||||||
|
return MD5.getHash(file);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static
|
||||||
|
byte[] hashFileSHA1(File file) {
|
||||||
|
SHA1Digest digest = new SHA1Digest();
|
||||||
|
return hashFile(file, digest, null);
|
||||||
|
}
|
||||||
|
|
||||||
public static final
|
public static
|
||||||
class Util {
|
byte[] hashFileSHA256(File file) {
|
||||||
|
SHA256Digest digest = new SHA256Digest();
|
||||||
|
return hashFile(file, digest, null);
|
||||||
|
}
|
||||||
|
|
||||||
// CUSTOM_HEADER USE
|
public static
|
||||||
private static final byte[] CUSTOM_HEADER = new byte[] {(byte) -2, (byte) -54, (byte) -54, (byte) -98};
|
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
|
* Return the hash of the file or NULL if file is invalid
|
||||||
*
|
*
|
||||||
* @param logger
|
* @param logger
|
||||||
* may be null, if no log output is necessary
|
* may be null, if no log output is necessary
|
||||||
*/
|
*/
|
||||||
public static
|
public static
|
||||||
byte[] hashFile(File file, Digest digest, Logger logger) {
|
byte[] hashFile(File file, Digest digest, Logger logger) {
|
||||||
return hashFile(file, digest, 0L, logger);
|
return hashFile(file, digest, 0L, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the hash of the file or NULL if file is invalid
|
* Return the hash of the file or NULL if file is invalid
|
||||||
*
|
*
|
||||||
* @param logger
|
* @param logger
|
||||||
* may be null, if no log output is necessary
|
* may be null, if no log output is necessary
|
||||||
*/
|
*/
|
||||||
public static
|
public static
|
||||||
byte[] hashFile(File file, Digest digest, long lengthFromEnd, Logger logger) {
|
byte[] hashFile(File file, Digest digest, long lengthFromEnd, Logger logger) {
|
||||||
return hashFile(file, digest, lengthFromEnd, null, logger);
|
return hashFile(file, digest, lengthFromEnd, null, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the hash of the file or NULL if the file is invalid. ALSO includes the hash of the 'extraData' if specified.
|
|
||||||
*
|
|
||||||
* @param logger
|
|
||||||
* may be null, if no log output is necessary
|
|
||||||
*/
|
|
||||||
public static
|
|
||||||
byte[] hashFile(File file, Digest digest, long lengthFromEnd, byte[] extraData, Logger logger) {
|
|
||||||
if (file.isFile() && file.canRead()) {
|
|
||||||
InputStream inputStream = null;
|
|
||||||
try {
|
|
||||||
inputStream = new FileInputStream(file);
|
|
||||||
long size = file.length();
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (inputStream != null) {
|
|
||||||
try {
|
|
||||||
inputStream.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extraData != null) {
|
|
||||||
digest.update(extraData, 0, extraData.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] digestBytes = new byte[digest.getDigestSize()];
|
|
||||||
|
|
||||||
digest.doFinal(digestBytes, 0);
|
|
||||||
return digestBytes;
|
|
||||||
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the hash of the file or NULL if the file is invalid. ALSO includes the hash of the 'extraData' if specified.
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
byte[] hashFile(File file, Digest digest, long lengthFromEnd, byte[] extraData, Logger logger) {
|
||||||
|
if (file.isFile() && file.canRead()) {
|
||||||
|
InputStream inputStream = null;
|
||||||
try {
|
try {
|
||||||
Enumeration<JarEntry> jarElements = jarDestFile.entries();
|
inputStream = new FileInputStream(file);
|
||||||
|
long size = file.length();
|
||||||
|
|
||||||
boolean okToHash;
|
if (lengthFromEnd > 0 && lengthFromEnd < size) {
|
||||||
boolean hasAction;
|
size -= lengthFromEnd;
|
||||||
byte[] buffer = new byte[2048];
|
}
|
||||||
int read;
|
|
||||||
|
int bufferSize = 4096;
|
||||||
|
byte[] buffer = new byte[bufferSize];
|
||||||
|
|
||||||
|
int readBytes;
|
||||||
digest.reset();
|
digest.reset();
|
||||||
|
|
||||||
while (jarElements.hasMoreElements()) {
|
while (size > 0) {
|
||||||
JarEntry jarEntry = jarElements.nextElement();
|
//noinspection NumericCastThatLosesPrecision
|
||||||
String name = jarEntry.getName();
|
int maxToRead = (int) Math.min(bufferSize, size);
|
||||||
okToHash = !jarEntry.isDirectory();
|
readBytes = inputStream.read(buffer, 0, maxToRead);
|
||||||
|
size -= readBytes;
|
||||||
|
|
||||||
if (!okToHash) {
|
if (readBytes == 0) {
|
||||||
continue;
|
//wtf. finally still gets called.
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// data with NO extra data will NOT BE HASHED
|
digest.update(buffer, 0, readBytes);
|
||||||
// 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 = LittleEndian.Int_.from(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(OS.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) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Unexpected extra data in zip assigned. Aborting");
|
if (logger != null) {
|
||||||
|
logger.error("Error hashing file: {}", file.getAbsolutePath(), e);
|
||||||
|
} else {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
jarDestFile.close();
|
if (inputStream != null) {
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extraData != null) {
|
||||||
|
digest.update(extraData, 0, extraData.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] digestBytes = new byte[digest.getDigestSize()];
|
byte[] digestBytes = new byte[digest.getDigestSize()];
|
||||||
|
|
||||||
digest.doFinal(digestBytes, 0);
|
digest.doFinal(digestBytes, 0);
|
||||||
return digestBytes;
|
return digestBytes;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hash an input stream, based on the specified digest
|
* Specifically, to return the hash of the ALL files/directories inside the jar, minus the action specified (LGPL) files.
|
||||||
*/
|
*/
|
||||||
public static
|
public static
|
||||||
byte[] hashStream(Digest digest, InputStream inputStream) throws IOException {
|
byte[] hashJarContentsExcludeAction(File jarDestFilename, Digest digest, int action) throws IOException {
|
||||||
|
JarFile jarDestFile = new JarFile(jarDestFilename);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Enumeration<JarEntry> jarElements = jarDestFile.entries();
|
||||||
|
|
||||||
|
boolean okToHash;
|
||||||
|
boolean hasAction;
|
||||||
byte[] buffer = new byte[2048];
|
byte[] buffer = new byte[2048];
|
||||||
int read;
|
int read;
|
||||||
digest.reset();
|
digest.reset();
|
||||||
|
|
||||||
|
while (jarElements.hasMoreElements()) {
|
||||||
|
JarEntry jarEntry = jarElements.nextElement();
|
||||||
|
String name = jarEntry.getName();
|
||||||
|
okToHash = !jarEntry.isDirectory();
|
||||||
|
|
||||||
while ((read = inputStream.read(buffer)) > 0) {
|
if (!okToHash) {
|
||||||
digest.update(buffer, 0, read);
|
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 = LittleEndian.Int_.from(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(OS.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);
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
inputStream.close();
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Unexpected extra data in zip assigned. Aborting");
|
||||||
byte[] digestBytes = new byte[digest.getDigestSize()];
|
} finally {
|
||||||
|
jarDestFile.close();
|
||||||
digest.doFinal(digestBytes, 0);
|
|
||||||
return digestBytes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
byte[] digestBytes = new byte[digest.getDigestSize()];
|
||||||
* 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.Util.charToBytesPassword(password);
|
|
||||||
|
|
||||||
return PBKDF2(charToBytes, salt, iterationCount);
|
digest.doFinal(digestBytes, 0);
|
||||||
}
|
return digestBytes;
|
||||||
|
|
||||||
/**
|
|
||||||
* 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(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private
|
|
||||||
Util() {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
package dorkbox.util.crypto;
|
||||||
|
|
||||||
import org.bouncycastle.crypto.BufferedBlockCipher;
|
import org.bouncycastle.crypto.BufferedBlockCipher;
|
||||||
|
@ -1,3 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
package dorkbox.util.crypto;
|
||||||
|
|
||||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||||
|
@ -1,3 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
package dorkbox.util.crypto;
|
||||||
|
|
||||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||||
|
815
Dorkbox-Util/src/dorkbox/util/crypto/CryptoPGP.java
Normal file
815
Dorkbox-Util/src/dorkbox/util/crypto/CryptoPGP.java
Normal file
@ -0,0 +1,815 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 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 dorkbox.util.OS;
|
||||||
|
import dorkbox.util.Sys;
|
||||||
|
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||||
|
import org.bouncycastle.bcpg.BCPGOutputStream;
|
||||||
|
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
|
||||||
|
import org.bouncycastle.openpgp.*;
|
||||||
|
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||||
|
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
|
||||||
|
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
|
||||||
|
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
|
||||||
|
import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
|
||||||
|
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
|
||||||
|
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.security.NoSuchProviderException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PGP crypto related methods
|
||||||
|
*/
|
||||||
|
public final
|
||||||
|
class CryptoPGP {
|
||||||
|
private static final BcPGPDigestCalculatorProvider digestCalculatorProvider = new BcPGPDigestCalculatorProvider();
|
||||||
|
private static final BcKeyFingerprintCalculator fingerprintCalculator = new BcKeyFingerprintCalculator();
|
||||||
|
|
||||||
|
|
||||||
|
// https://github.com/weiliatgithub/bouncycastle-gpg-exampleC
|
||||||
|
// https://gist.github.com/turingbirds/3df43f1920a98010667a
|
||||||
|
// http://sloanseaman.com/wordpress/2012/05/13/revisited-pgp-encryptiondecryption-in-java/
|
||||||
|
// http://bouncycastle-pgp-cookbook.blogspot.de/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign a message using our private PGP key file, this matches gpg -ab "hello.txt"
|
||||||
|
*
|
||||||
|
* @param privateKeyInputStream
|
||||||
|
* this is an armored key file, not a binary stream
|
||||||
|
* @param userId
|
||||||
|
* this is the userID to get out of the private key
|
||||||
|
* @param password
|
||||||
|
* this is the password to unlock the private key
|
||||||
|
* @param messageAsUtf8Bytes
|
||||||
|
* this is the message, in bytes, to sign
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
byte[] signGpgCompatible(InputStream privateKeyInputStream, String userId, char[] password, byte[] messageAsUtf8Bytes)
|
||||||
|
throws PGPException {
|
||||||
|
|
||||||
|
// the signature type (in gpg terms), is "sigclass". gpg is BINARY_DOC (0x00)
|
||||||
|
return sign(privateKeyInputStream,
|
||||||
|
userId,
|
||||||
|
password,
|
||||||
|
new ByteArrayInputStream(messageAsUtf8Bytes),
|
||||||
|
PGPSignature.BINARY_DOCUMENT,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign a message using our private PGP key file, this matches gpg -ab "hello.txt"
|
||||||
|
*
|
||||||
|
* @param privateKeyInputStream
|
||||||
|
* this is an armored key file, not a binary stream
|
||||||
|
* @param userId
|
||||||
|
* this is the userID to get out of the private key
|
||||||
|
* @param password
|
||||||
|
* this is the password to unlock the private key
|
||||||
|
* @param message
|
||||||
|
* this is the message to sign
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
byte[] signGpgCompatible(InputStream privateKeyInputStream, String userId, char[] password, InputStream message)
|
||||||
|
throws PGPException {
|
||||||
|
|
||||||
|
// the signature type (in gpg terms), is "sigclass". gpg is BINARY_DOC (0x00)
|
||||||
|
return sign(privateKeyInputStream,
|
||||||
|
userId,
|
||||||
|
password,
|
||||||
|
message,
|
||||||
|
PGPSignature.BINARY_DOCUMENT,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign a message using our private PGP key file, this matches gpg -ab "hello.txt". This will save the signature of the passed-in
|
||||||
|
* file to file name + .asc
|
||||||
|
*
|
||||||
|
* @param privateKeyInputStream
|
||||||
|
* this is an armored key file, not a binary stream
|
||||||
|
* @param userId
|
||||||
|
* this is the userID to get out of the private key
|
||||||
|
* @param password
|
||||||
|
* this is the password to unlock the private key
|
||||||
|
* @param file
|
||||||
|
* this is the file to sign
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
void signGpgCompatible(InputStream privateKeyInputStream, String userId, char[] password, File file)
|
||||||
|
throws PGPException {
|
||||||
|
|
||||||
|
// the signature type (in gpg terms), is "sigclass". gpg is BINARY_DOC (0x00)
|
||||||
|
final byte[] sign = sign(privateKeyInputStream,
|
||||||
|
userId,
|
||||||
|
password,
|
||||||
|
file,
|
||||||
|
PGPSignature.BINARY_DOCUMENT,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false);
|
||||||
|
|
||||||
|
FileOutputStream fileOutputStream1 = null;
|
||||||
|
try {
|
||||||
|
fileOutputStream1 = new FileOutputStream(new File(file.getAbsolutePath() + ".asc"));
|
||||||
|
fileOutputStream1.write(sign);
|
||||||
|
fileOutputStream1.flush();
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
throw new PGPException("Unable to save signature to file " + file.getAbsolutePath() + ".asc", e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new PGPException("Unable to save signature to file " + file.getAbsolutePath() + ".asc", e);
|
||||||
|
} finally {
|
||||||
|
Sys.close(fileOutputStream1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign a message using our private PGP key file, with a variety of options
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("Duplicates")
|
||||||
|
public static
|
||||||
|
byte[] sign(InputStream privateKeyInputStream,
|
||||||
|
String userId,
|
||||||
|
char[] password,
|
||||||
|
InputStream message,
|
||||||
|
int signatureType,
|
||||||
|
boolean compressSignature,
|
||||||
|
boolean asciiArmoredOutput,
|
||||||
|
boolean includeDataInSignature,
|
||||||
|
boolean generateUserIdSubPacket,
|
||||||
|
boolean generateOnePassVersion) throws PGPException {
|
||||||
|
|
||||||
|
List<PGPSecretKey> secretKeys = getSecretKeys(privateKeyInputStream, userId);
|
||||||
|
PGPSignatureGenerator signature = createSignature(secretKeys, password, signatureType, generateUserIdSubPacket);
|
||||||
|
|
||||||
|
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
|
OutputStream outputStream = byteArrayOutputStream;
|
||||||
|
if (asciiArmoredOutput) {
|
||||||
|
outputStream = new ArmoredOutputStream(byteArrayOutputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
PGPCompressedDataGenerator compressedDataGenerator = null;
|
||||||
|
BCPGOutputStream bcOutputStream;
|
||||||
|
|
||||||
|
if (compressSignature) {
|
||||||
|
compressedDataGenerator = new PGPCompressedDataGenerator(PGPCompressedData.ZLIB);
|
||||||
|
try {
|
||||||
|
bcOutputStream = new BCPGOutputStream(compressedDataGenerator.open(outputStream));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new PGPException("Unable to open compression stream in the signature", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bcOutputStream = new BCPGOutputStream(outputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (generateOnePassVersion) {
|
||||||
|
try {
|
||||||
|
signature.generateOnePassVersion(false)
|
||||||
|
.encode(bcOutputStream);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new PGPException("Unable to generate OnePass signature header", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PGPLiteralDataGenerator literalDataGenerator = null;
|
||||||
|
OutputStream literalDataOutput = null;
|
||||||
|
|
||||||
|
if (includeDataInSignature) {
|
||||||
|
literalDataGenerator = new PGPLiteralDataGenerator();
|
||||||
|
try {
|
||||||
|
literalDataOutput = literalDataGenerator.open(bcOutputStream,
|
||||||
|
PGPLiteralData.BINARY,
|
||||||
|
"_CONSOLE",
|
||||||
|
message.available(),
|
||||||
|
new Date());
|
||||||
|
} catch (IOException e1) {
|
||||||
|
throw new PGPException("Unable to generate Literal Data signature header", e1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int read;
|
||||||
|
|
||||||
|
// update bytes in the streams
|
||||||
|
if (literalDataOutput != null) {
|
||||||
|
while ((read = message.read(buffer)) > 0) {
|
||||||
|
literalDataOutput.write(buffer, 0, read);
|
||||||
|
signature.update(buffer, 0, read);
|
||||||
|
}
|
||||||
|
literalDataOutput.flush();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
while ((read = message.read(buffer)) > 0) {
|
||||||
|
signature.update(buffer, 0, read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// close generators and update signature
|
||||||
|
if (literalDataGenerator != null) {
|
||||||
|
literalDataGenerator.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
signature.generate()
|
||||||
|
.encode(bcOutputStream);
|
||||||
|
|
||||||
|
|
||||||
|
if (compressedDataGenerator != null) {
|
||||||
|
compressedDataGenerator.close();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
Sys.close(bcOutputStream);
|
||||||
|
Sys.close(outputStream);
|
||||||
|
Sys.close(literalDataOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
return byteArrayOutputStream.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign a message using our private PGP key file, with a variety of options
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("Duplicates")
|
||||||
|
public static
|
||||||
|
byte[] sign(InputStream privateKeyInputStream,
|
||||||
|
String userId,
|
||||||
|
char[] password,
|
||||||
|
File fileMessage,
|
||||||
|
int signatureType,
|
||||||
|
boolean compressSignature,
|
||||||
|
boolean asciiArmoredOutput,
|
||||||
|
boolean includeDataInSignature,
|
||||||
|
boolean generateUserIdSubPacket,
|
||||||
|
boolean generateOnePassVersion) throws PGPException {
|
||||||
|
|
||||||
|
List<PGPSecretKey> secretKeys = getSecretKeys(privateKeyInputStream, userId);
|
||||||
|
PGPSignatureGenerator signature = createSignature(secretKeys, password, signatureType, generateUserIdSubPacket);
|
||||||
|
|
||||||
|
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
|
OutputStream outputStream = byteArrayOutputStream;
|
||||||
|
if (asciiArmoredOutput) {
|
||||||
|
outputStream = new ArmoredOutputStream(byteArrayOutputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
PGPCompressedDataGenerator compressedDataGenerator = null;
|
||||||
|
BCPGOutputStream bcOutputStream;
|
||||||
|
|
||||||
|
if (compressSignature) {
|
||||||
|
compressedDataGenerator = new PGPCompressedDataGenerator(PGPCompressedData.ZLIB);
|
||||||
|
try {
|
||||||
|
bcOutputStream = new BCPGOutputStream(compressedDataGenerator.open(outputStream));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new PGPException("Unable to open compression stream in the signature", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bcOutputStream = new BCPGOutputStream(outputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (generateOnePassVersion) {
|
||||||
|
try {
|
||||||
|
signature.generateOnePassVersion(false)
|
||||||
|
.encode(bcOutputStream);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new PGPException("Unable to generate OnePass signature header", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PGPLiteralDataGenerator literalDataGenerator = null;
|
||||||
|
OutputStream literalDataOutput = null;
|
||||||
|
|
||||||
|
if (includeDataInSignature) {
|
||||||
|
literalDataGenerator = new PGPLiteralDataGenerator();
|
||||||
|
try {
|
||||||
|
literalDataOutput = literalDataGenerator.open(bcOutputStream,
|
||||||
|
PGPLiteralData.BINARY,
|
||||||
|
fileMessage);
|
||||||
|
} catch (IOException e1) {
|
||||||
|
throw new PGPException("Unable to generate Literal Data signature header", e1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final FileInputStream fileInputStream = new FileInputStream(fileMessage);
|
||||||
|
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int read;
|
||||||
|
|
||||||
|
// update bytes in the streams
|
||||||
|
if (literalDataOutput != null) {
|
||||||
|
while ((read = fileInputStream.read(buffer)) > 0) {
|
||||||
|
literalDataOutput.write(buffer, 0, read);
|
||||||
|
signature.update(buffer, 0, read);
|
||||||
|
}
|
||||||
|
literalDataOutput.flush();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
while ((read = fileInputStream.read(buffer)) > 0) {
|
||||||
|
signature.update(buffer, 0, read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// close generators and update signature
|
||||||
|
if (literalDataGenerator != null) {
|
||||||
|
literalDataGenerator.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
signature.generate()
|
||||||
|
.encode(bcOutputStream);
|
||||||
|
|
||||||
|
|
||||||
|
if (compressedDataGenerator != null) {
|
||||||
|
compressedDataGenerator.close();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
Sys.close(bcOutputStream);
|
||||||
|
Sys.close(outputStream);
|
||||||
|
Sys.close(literalDataOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
return byteArrayOutputStream.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find private gpg key in InputStream, also closes the input stream
|
||||||
|
*
|
||||||
|
* @param inputStream
|
||||||
|
* the inputStream that contains the private (secret) key
|
||||||
|
* @param userId
|
||||||
|
* the user id
|
||||||
|
*
|
||||||
|
* @return the PGP secret key
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
List<PGPSecretKey> getSecretKeys(InputStream inputStream, String userId) throws PGPException {
|
||||||
|
// iterate over every private key in the key ring
|
||||||
|
PGPSecretKeyRingCollection secretKeyRings;
|
||||||
|
try {
|
||||||
|
secretKeyRings = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(inputStream), fingerprintCalculator);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new PGPException("No private key found in stream!", e);
|
||||||
|
} finally {
|
||||||
|
Sys.close(inputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
// look for the key ring that is used to authenticate our reporting facilities
|
||||||
|
Iterator<PGPSecretKeyRing> secretKeys = secretKeyRings.getKeyRings(userId);
|
||||||
|
List<PGPSecretKey> pgpSecretKeys = new ArrayList<PGPSecretKey>();
|
||||||
|
|
||||||
|
// iterate over every private key in the ring
|
||||||
|
while (secretKeys.hasNext()) {
|
||||||
|
PGPSecretKeyRing secretKeyRing = secretKeys.next();
|
||||||
|
PGPSecretKey tmpKey = secretKeyRing.getSecretKey();
|
||||||
|
|
||||||
|
if (tmpKey != null) {
|
||||||
|
pgpSecretKeys.add(tmpKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pgpSecretKeys.isEmpty()) {
|
||||||
|
return pgpSecretKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new PGPException("No private key found in stream!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the signature that will be used to PGP sign data
|
||||||
|
*
|
||||||
|
* @param secretKeys
|
||||||
|
* these are the secret keys
|
||||||
|
* @param password
|
||||||
|
* this is the password to unlock the secret key
|
||||||
|
*
|
||||||
|
* @return the signature used to sign data
|
||||||
|
*
|
||||||
|
* @throws PGPException
|
||||||
|
*/
|
||||||
|
private static
|
||||||
|
PGPSignatureGenerator createSignature(List<PGPSecretKey> secretKeys,
|
||||||
|
char[] password,
|
||||||
|
int signatureType,
|
||||||
|
boolean generateUserIdSubPacket) throws PGPException {
|
||||||
|
|
||||||
|
PGPSecretKey secretKey = null;
|
||||||
|
for (int i = 0; i < secretKeys.size(); i++) {
|
||||||
|
secretKey = secretKeys.get(i);
|
||||||
|
|
||||||
|
// we ONLY want the signing master key
|
||||||
|
if (!secretKey.isSigningKey() || !secretKey.isMasterKey()) {
|
||||||
|
secretKey = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (secretKey == null) {
|
||||||
|
throw new PGPException("Secret key is not the signing master key");
|
||||||
|
}
|
||||||
|
|
||||||
|
// System.err.println("Signing key = " + tmpKey.isSigningKey() +", Master key = " + tmpKey.isMasterKey() + ", UserId = " +
|
||||||
|
// userId );
|
||||||
|
|
||||||
|
if (password == null) {
|
||||||
|
password = new char[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
PBESecretKeyDecryptor build = new BcPBESecretKeyDecryptorBuilder(digestCalculatorProvider).build(password);
|
||||||
|
|
||||||
|
SecureRandom random = new SecureRandom();
|
||||||
|
BcPGPContentSignerBuilder bcPGPContentSignerBuilder = new BcPGPContentSignerBuilder(secretKey.getPublicKey()
|
||||||
|
.getAlgorithm(),
|
||||||
|
PGPUtil.SHA1).setSecureRandom(random);
|
||||||
|
|
||||||
|
PGPSignatureGenerator signature = new PGPSignatureGenerator(bcPGPContentSignerBuilder);
|
||||||
|
signature.init(signatureType, secretKey.extractPrivateKey(build));
|
||||||
|
|
||||||
|
Iterator userIds = secretKey.getPublicKey()
|
||||||
|
.getUserIDs();
|
||||||
|
|
||||||
|
// use the first userId that matches
|
||||||
|
if (userIds.hasNext()) {
|
||||||
|
if (generateUserIdSubPacket) {
|
||||||
|
PGPSignatureSubpacketGenerator subpacketGenerator = new PGPSignatureSubpacketGenerator();
|
||||||
|
subpacketGenerator.setSignerUserID(false, (String) userIds.next());
|
||||||
|
signature.setHashedSubpackets(subpacketGenerator.generate());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
signature.setHashedSubpackets(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new PGPException("Did not find specified userId");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a PGP public key block and return the keyring it represents.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
PGPPublicKeyRing getKeyring(InputStream keyBlockStream) throws IOException {
|
||||||
|
|
||||||
|
BcKeyFingerprintCalculator keyfp = new BcKeyFingerprintCalculator();
|
||||||
|
|
||||||
|
// PGPUtil.getDecoderStream() will detect ASCII-armor automatically and decode it,
|
||||||
|
// the PGPObject factory then knows how to read all the data in the encoded stream
|
||||||
|
PGPObjectFactory factory = new PGPObjectFactory(PGPUtil.getDecoderStream(keyBlockStream), keyfp);
|
||||||
|
|
||||||
|
// these files should really just have one object in them, and that object should be a PGPPublicKeyRing.
|
||||||
|
Object o = factory.nextObject();
|
||||||
|
if (o instanceof PGPPublicKeyRing) {
|
||||||
|
return (PGPPublicKeyRing) o;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Input stream does not contain a PGP Public Key");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the first encryption key from the given keyring.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
PGPPublicKey getEncryptionKey(PGPPublicKeyRing keyRing) {
|
||||||
|
if (keyRing == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate over the keys on the ring, look for one which is suitable for encryption.
|
||||||
|
Iterator keys = keyRing.getPublicKeys();
|
||||||
|
PGPPublicKey key;
|
||||||
|
while (keys.hasNext()) {
|
||||||
|
key = (PGPPublicKey) keys.next();
|
||||||
|
if (key.isEncryptionKey()) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the first decryption key from the given keyring.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
PGPSecretKey getDecryptionKey(PGPSecretKeyRing keyRing) {
|
||||||
|
if (keyRing == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate over the keys on the ring, look for one which is suitable for encryption.
|
||||||
|
Iterator keys = keyRing.getSecretKeys();
|
||||||
|
PGPSecretKey key;
|
||||||
|
while (keys.hasNext()) {
|
||||||
|
key = (PGPSecretKey) keys.next();
|
||||||
|
if (key.isMasterKey()) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt plaintext message using public key from publickeyFile.
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
* the message
|
||||||
|
*
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
|
private
|
||||||
|
String encrypt(InputStream publicKeyInputStream, String message) throws PGPException, IOException, NoSuchProviderException {
|
||||||
|
// find the PGP key in the file
|
||||||
|
PGPPublicKey publicKey = findPublicGPGKey(publicKeyInputStream);
|
||||||
|
|
||||||
|
if (publicKey == null) {
|
||||||
|
System.err.println("Did not find public GPG key");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Encode the string into bytes using utf-8
|
||||||
|
byte[] utf8Bytes = message.getBytes(OS.UTF_8);
|
||||||
|
|
||||||
|
ByteArrayOutputStream compressedOutput = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
// compress bytes with zip
|
||||||
|
PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator();
|
||||||
|
|
||||||
|
// the reason why we compress here is GPG not being able to decrypt our message input but if we do not compress.
|
||||||
|
// I guess pkzip compression also encodes only to GPG-friendly characters.
|
||||||
|
PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP);
|
||||||
|
try {
|
||||||
|
OutputStream literalDataOutput = literalDataGenerator.open(compressedOutput,
|
||||||
|
PGPLiteralData.BINARY,
|
||||||
|
"_CONSOLE",
|
||||||
|
utf8Bytes.length,
|
||||||
|
new Date());
|
||||||
|
// update bytes in the stream
|
||||||
|
literalDataOutput.write(utf8Bytes);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// catch but close the streams in finally
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
compressedDataGenerator.close();
|
||||||
|
Sys.close(compressedOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
SecureRandom random = new SecureRandom();
|
||||||
|
|
||||||
|
// now we have zip-compressed bytes
|
||||||
|
byte[] compressedBytes = compressedOutput.toByteArray();
|
||||||
|
|
||||||
|
BcPGPDataEncryptorBuilder bcPGPDataEncryptorBuilder = new BcPGPDataEncryptorBuilder(PGPEncryptedData.CAST5)
|
||||||
|
.setWithIntegrityPacket(true)
|
||||||
|
.setSecureRandom(random);
|
||||||
|
|
||||||
|
PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(bcPGPDataEncryptorBuilder);
|
||||||
|
|
||||||
|
// use public key to encrypt data
|
||||||
|
|
||||||
|
BcPublicKeyKeyEncryptionMethodGenerator encKeyGen = new BcPublicKeyKeyEncryptionMethodGenerator(publicKey)
|
||||||
|
.setSecureRandom(random);
|
||||||
|
|
||||||
|
encryptedDataGenerator.addMethod(encKeyGen);
|
||||||
|
|
||||||
|
// literalDataOutput --> compressedOutput --> ArmoredOutputStream --> ByteArrayOutputStream
|
||||||
|
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
|
ArmoredOutputStream armoredOut = new ArmoredOutputStream(byteArrayOutputStream);
|
||||||
|
OutputStream encryptedOutput = null;
|
||||||
|
try {
|
||||||
|
encryptedOutput = encryptedDataGenerator.open(armoredOut, compressedBytes.length);
|
||||||
|
encryptedOutput.write(compressedBytes);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (PGPException e) {
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
Sys.close(encryptedOutput);
|
||||||
|
Sys.close(armoredOut);
|
||||||
|
}
|
||||||
|
String encrypted = new String(byteArrayOutputStream.toByteArray());
|
||||||
|
|
||||||
|
System.err.println("Message: " + message);
|
||||||
|
System.err.println("Encrypted: " + encrypted);
|
||||||
|
|
||||||
|
return encrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find public gpg key in InputStream.
|
||||||
|
*
|
||||||
|
* @param inputStream
|
||||||
|
* the input stream
|
||||||
|
*
|
||||||
|
* @return the PGP public key
|
||||||
|
*/
|
||||||
|
private static
|
||||||
|
PGPPublicKey findPublicGPGKey(InputStream inputStream) throws IOException, PGPException {
|
||||||
|
|
||||||
|
// get all key rings in the input stream
|
||||||
|
PGPPublicKeyRingCollection publicKeyRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(inputStream), fingerprintCalculator);
|
||||||
|
|
||||||
|
System.err.println("key ring size: " + publicKeyRingCollection.size());
|
||||||
|
|
||||||
|
Iterator<PGPPublicKeyRing> keyRingIter = publicKeyRingCollection.getKeyRings();
|
||||||
|
|
||||||
|
// iterate over keyrings
|
||||||
|
while (keyRingIter.hasNext()) {
|
||||||
|
PGPPublicKeyRing keyRing = keyRingIter.next();
|
||||||
|
Iterator<PGPPublicKey> keyIter = keyRing.getPublicKeys();
|
||||||
|
// iterate over public keys in the key ring
|
||||||
|
while (keyIter.hasNext()) {
|
||||||
|
PGPPublicKey tmpKey = keyIter.next();
|
||||||
|
|
||||||
|
if (tmpKey == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<String> userIDs = tmpKey.getUserIDs();
|
||||||
|
ArrayList<String> strings = new ArrayList<String>();
|
||||||
|
while (userIDs.hasNext()) {
|
||||||
|
String next = userIDs.next();
|
||||||
|
strings.add(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.err.println(
|
||||||
|
"Encryption key = " + tmpKey.isEncryptionKey() + ", Master key = " + tmpKey.isMasterKey() + ", UserId = " +
|
||||||
|
strings);
|
||||||
|
|
||||||
|
// we need a master encryption key
|
||||||
|
if (tmpKey.isEncryptionKey() && tmpKey.isMasterKey()) {
|
||||||
|
return tmpKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new PGPException("No public key found!");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static
|
||||||
|
void verify(final InputStream publicKeyInputStream, final byte[] signature) throws Exception {
|
||||||
|
PGPPublicKey publicKey = findPublicGPGKey(publicKeyInputStream);
|
||||||
|
|
||||||
|
String text = new String(signature);
|
||||||
|
|
||||||
|
Pattern regex = Pattern.compile(
|
||||||
|
"-----BEGIN PGP SIGNED MESSAGE-----\\r?\\n.*?\\r?\\n\\r?\\n(.*)\\r?\\n(-----BEGIN PGP SIGNATURE-----\\r?\\n.*-----END PGP SIGNATURE-----)",
|
||||||
|
Pattern.CANON_EQ | Pattern.DOTALL);
|
||||||
|
Matcher regexMatcher = regex.matcher(text);
|
||||||
|
if (regexMatcher.find()) {
|
||||||
|
String dataText = regexMatcher.group(1);
|
||||||
|
String signText = regexMatcher.group(2);
|
||||||
|
|
||||||
|
ByteArrayInputStream dataIn = new ByteArrayInputStream(dataText.getBytes("UTF8"));
|
||||||
|
ByteArrayInputStream signIn = new ByteArrayInputStream(signText.getBytes("UTF8"));
|
||||||
|
|
||||||
|
|
||||||
|
InputStream signIn2 = PGPUtil.getDecoderStream(signIn);
|
||||||
|
|
||||||
|
PGPObjectFactory pgpFact = new PGPObjectFactory(signIn2, new BcKeyFingerprintCalculator());
|
||||||
|
PGPSignatureList p3 = null;
|
||||||
|
|
||||||
|
Object o;
|
||||||
|
|
||||||
|
try {
|
||||||
|
o = pgpFact.nextObject();
|
||||||
|
if (o == null) {
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new Exception("Invalid input data");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (o instanceof PGPCompressedData) {
|
||||||
|
PGPCompressedData c1 = (PGPCompressedData) o;
|
||||||
|
|
||||||
|
pgpFact = new PGPObjectFactory(c1.getDataStream(), new BcKeyFingerprintCalculator());
|
||||||
|
|
||||||
|
p3 = (PGPSignatureList) pgpFact.nextObject();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
p3 = (PGPSignatureList) o;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// PGPSignature sig = p3.get(0);
|
||||||
|
// PGPPublicKey key = KeyRing.getPublicKeyByID(sig.getKeyID());
|
||||||
|
//
|
||||||
|
// if (key == null)
|
||||||
|
// throw new Exception("Cannot find key 0x" + Integer.toHexString((int) sig.getKeyID()).toUpperCase() + " in the pubring");
|
||||||
|
//
|
||||||
|
// sig.initVerify(key, "BC");
|
||||||
|
//
|
||||||
|
// while ((ch = dataIn.read()) >= 0) {
|
||||||
|
// sig.update((byte) ch); //TODO migliorabile con byte[]
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (sig.verify())
|
||||||
|
// return new PrintablePGPPublicKey(key).toString();
|
||||||
|
// else
|
||||||
|
// return null;
|
||||||
|
|
||||||
|
// return verifyFile(dataIn, signIn);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private
|
||||||
|
CryptoPGP() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static
|
||||||
|
void main(String[] args) throws Exception {
|
||||||
|
InputStream privateKeyInputStream = new FileInputStream(new File("/home/user/dorkbox/sonatype_private.key"));
|
||||||
|
|
||||||
|
byte[] textBytes = "hello".getBytes(OS.UTF_8);
|
||||||
|
|
||||||
|
byte[] bytes = CryptoPGP.signGpgCompatible(privateKeyInputStream, "Dorkbox <sonatype@dorkbox.com>", new char[0], textBytes);
|
||||||
|
|
||||||
|
// String s = new String(hello);
|
||||||
|
// String s1 = s.replaceAll("\n", "\r\n");
|
||||||
|
// byte[] bytes = s1.getBytes(OS.UTF_8);
|
||||||
|
|
||||||
|
//
|
||||||
|
// String signed = new String(bytes);
|
||||||
|
//
|
||||||
|
// System.err.println("Message: " + new String(messageAsUtf8Bytes));
|
||||||
|
// System.err.println("Signature: " + signed);
|
||||||
|
//
|
||||||
|
// return bytes;
|
||||||
|
|
||||||
|
// String s2 = new String(bytes);
|
||||||
|
|
||||||
|
|
||||||
|
// InputStream publicKeyInputStream = new FileInputStream(new File("/home/user/dorkbox/sonatype_public.key"));
|
||||||
|
// cryptoPGP.verify(publicKeyInputStream, hello);
|
||||||
|
|
||||||
|
|
||||||
|
FileOutputStream fileOutputStream = new FileOutputStream(new File("/home/user/dorkbox/hello2.txt"));
|
||||||
|
fileOutputStream.write(textBytes);
|
||||||
|
fileOutputStream.flush();
|
||||||
|
Sys.close(fileOutputStream);
|
||||||
|
|
||||||
|
|
||||||
|
FileOutputStream fileOutputStream1 = new FileOutputStream(new File("/home/user/dorkbox/hello2.txt.asc"));
|
||||||
|
fileOutputStream1.write(bytes);
|
||||||
|
fileOutputStream1.flush();
|
||||||
|
Sys.close(fileOutputStream1);
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
package dorkbox.util.crypto;
|
||||||
|
|
||||||
import org.bouncycastle.crypto.AsymmetricBlockCipher;
|
import org.bouncycastle.crypto.AsymmetricBlockCipher;
|
||||||
|
@ -1,3 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
package dorkbox.util.crypto;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
@ -86,7 +101,7 @@ class CryptoSCrypt {
|
|||||||
String encrypt(char[] password, byte[] salt, int N, int r, int p, int dkLen) {
|
String encrypt(char[] password, byte[] salt, int N, int r, int p, int dkLen) {
|
||||||
// Note: this saves the char array in UTF-16 format of bytes.
|
// Note: this saves the char array in UTF-16 format of bytes.
|
||||||
// can't use password after this as it's been changed to '*'
|
// can't use password after this as it's been changed to '*'
|
||||||
byte[] passwordBytes = Crypto.Util.charToBytesPassword(password);
|
byte[] passwordBytes = Crypto.charToBytesPassword_UTF16(password);
|
||||||
|
|
||||||
byte[] derived = encrypt(passwordBytes, salt, N, r, p, dkLen);
|
byte[] derived = encrypt(passwordBytes, salt, N, r, p, dkLen);
|
||||||
|
|
||||||
@ -118,7 +133,7 @@ class CryptoSCrypt {
|
|||||||
boolean verify(char[] password, String hashed) {
|
boolean verify(char[] password, String hashed) {
|
||||||
// Note: this saves the char array in UTF-16 format of bytes.
|
// Note: this saves the char array in UTF-16 format of bytes.
|
||||||
// can't use password after this as it's been changed to '*'
|
// can't use password after this as it's been changed to '*'
|
||||||
byte[] passwordBytes = Crypto.Util.charToBytesPassword(password);
|
byte[] passwordBytes = Crypto.charToBytesPassword_UTF16(password);
|
||||||
|
|
||||||
String[] parts = hashed.split("\\$");
|
String[] parts = hashed.split("\\$");
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user