SHA-256 with salt for hashing produces different values for signature in java - sha256

We have to build a signature string hashed using SHA-256 with random salt and using 10000 iterations. However, when I run the below code with salt, output hash value is not matching at all for the same input string. If I run the code without the salt, hashed value matches. Can someone point out the right way to add a random salt to the SHA-256 digest and get the firstValue/secondValue same below.
import java.security.MessageDigest;
import java.security.SecureRandom;
import org.springframework.security.crypto.codec.Hex;
public class someTester{
public static void main(String[] args) {
String signatureInput = "someStringWhichisSensitive";
String firstSignValue = getSignature(signatureInput);
String secondSignValue = getSignature(signatureInput);
System.out.println("firstSignValue="+firstSignValue);
System.out.println("secondSignValue="+secondSignValue);
}
private static String getSignature(String signatureInput){
MessageDigest md;
String signatureValue = null;
try {
// Create a random salt
SecureRandom sr = new SecureRandom();
byte[] bSalt = new byte[8];
sr.nextBytes(bSalt);
md = MessageDigest.getInstance("SHA-256");
// digest.update(bSalt);
byte[] bDigest = md.digest(signatureInput.getBytes());
// Iterate through 10000 times
for (int i = 0; i < 10000; i++) {
bDigest = md.digest(bDigest);
}
signatureValue = new String(Hex.encode(bDigest));
} catch (Exception e) {
System.out.println("Exception while calculating SHA-256 digest value"+e);
}
return signatureValue;
}
}

Related

JDK 16 Curve not supported: secp256r1 [NIST P-256,X9.62 prime256v1] (1.2.840.10045.3.1.7)

Currently working on upgrading from Java 13 to Java 16 and using the following updated dependencies for a cryptography library performing encryption/decryption:
BouncyCastle 1.69
Google Tink 1.6.1
=================================
Crypto Library class:
package com.decryption.test;
#NoArgsConstructor
#Service
public class CryptoLib {
protected static final String PROTOCOL_VERSION = "ECv2";
protected final String DEV_ROOT_PUB_KEY = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESXy7kqanQwAM/HBTcV0MVtgQkKLY6UVqP3Z/vdlxiRgFqnc9dZUSD8muUpgeZDD05lM68qoI31mbeX9c8P/9Uw==";
protected static final String MERCHANT_ID_PREFIX = "merchant:";
protected static final String HMAC_SHA256_ALGO = "HmacSha256";
protected static final int HMAC_SHA256_ALGO_SIZE = 256 / 8; // 256 bit key size -> 32 bytes
protected static final String AES_CTR_ALGO = "AES/CTR/NoPadding";
protected static final int AES_CTR_ALGO_SIZE = 256 / 8; // 256 bit key size -> 32 bytes
protected static final byte[] HKDF_EMPTY_SALT = new byte[0];
protected static final byte[] AES_CTR_ZERO_IV = new byte[16];
protected final static String ASYMMETRIC_KEY_TYPE = "EC";
protected static final String SENDER_ID = "Google";
protected static final String SECURITY_PROVIDER = "BC";
protected static final String EC_CURVE = "prime256v1";
protected static final String CSR_SIGNATURE = "SHA256WITHECDSA";
protected static final String SIGNATURE_ALGORITHM = "SHA256withECDSA";
#Autowired
private ProfileManager profileManager;
#PostConstruct
public void loadKeys() {
final StringBuilder message = new StringBuilder("Loading Google pay signing keys. ");
if (this.profileManager.isProfileActive("dev")) {
message.append("Profile DEV is active. Only test rig encrypted payloads supported.");
} else {
final GooglePaymentsPublicKeysManager googleKeyManager = this.getGoogleKeyManager();
googleKeyManager.refreshInBackground();
}
System.out.println(message);
}
private static String getMerchantIdComponent(final String merchantId) {
return merchantId.startsWith(MERCHANT_ID_PREFIX) ?
merchantId : MERCHANT_ID_PREFIX + merchantId;
}
private GooglePaymentsPublicKeysManager getGoogleKeyManager() {
return profileManager.isProfileActive("prod") ?
GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION :
GooglePaymentsPublicKeysManager.INSTANCE_TEST;
}
public String decrypt(final GoogleDecryptRequest decryptRequest) throws Exception {
try {
final PaymentMethodTokenRecipient.Builder builder = new PaymentMethodTokenRecipient.Builder()
.protocolVersion(PROTOCOL_VERSION)
.recipientId(getMerchantIdComponent(decryptRequest.getMerchantId()));
if (decryptRequest.getPrivateKey() != null) {
builder.addRecipientPrivateKey(decryptRequest.getPrivateKey());
}
//Add all our retired keys to the list.
for (final ECPrivateKey ecPrivateKey : decryptRequest.getOldPrivateKeys()) {
builder.addRecipientPrivateKey(ecPrivateKey);
}
if (this.profileManager.isProfileActive("dev")) {
builder.addSenderVerifyingKey(DEV_ROOT_PUB_KEY);
} else {
builder.fetchSenderVerifyingKeysWith(getGoogleKeyManager());
}
return builder.build()
.unseal(decryptRequest.getEncryptedMessage());
} catch (final Exception e) {
final String errMsg = MessageFormat.format("GooglePay Decryption failed for Google merchant ID [{0}]", decryptRequest.getMerchantId());
throw new Exception(errMsg, e);
}
}
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
static class GoogleEncryptRequest implements EncryptRequest<ECPublicKey> {
/**
* Merchant public key
*/
private ECPublicKey publicKey;
private String algorithm = "EC";
/**
* Google merchantID. If the merchant ID is xxx, this value should be either
* <ul>
* <li>xxx</li>
* <li>merchant:xxx</li>
* </ul>
*/
private String merchantId;
/**
* Message to encrypt
*/
private String message;
}
public EncryptResponse encrypt(final GoogleEncryptRequest encryptRequest) throws Exception {
final String DEV_ROOT_PRIV_KEY = "MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg3lO35/XiUzEooUJlLKEd0BJmoLgeTvkFSm/b4wMrgdWgCgYIKoZIzj0DAQehRANCAARJfLuSpqdDAAz8cFNxXQxW2BCQotjpRWo/dn+92XGJGAWqdz11lRIPya5SmB5kMPTmUzryqgjfWZt5f1zw//1T";
final String DEV_ROOT_PUB_KEY = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESXy7kqanQwAM/HBTcV0MVtgQkKLY6UVqP3Z/vdlxiRgFqnc9dZUSD8muUpgeZDD05lM68qoI31mbeX9c8P/9Uw==";
final GoogleEncryptResponse response = new GoogleEncryptResponse();
try {
//STEP 1: Generate all keys.
// Generate root keypair. Used to sign intermediate. jsonPath -> $.intermediateSigningKey.signatures
final PrivateKey rootPrivKey = CryptoUtility.getPrivateFromPKCS8Base64(DEV_ROOT_PRIV_KEY, ASYMMETRIC_KEY_TYPE);
final PublicKey rootPubKey = CryptoUtility.getPublicFromBase64(DEV_ROOT_PUB_KEY, ASYMMETRIC_KEY_TYPE); //equiv to the verifying key from the test/prod APIs
// Generate intermediate keypair. Used to sign whole payload. jsonPath -> $.signature
final KeyPair intermediateKeyPair = generateKeyPair();
final PrivateKey intermediatePrivKey = intermediateKeyPair.getPrivate();
// Convert to uncompressed point and B64 encode. Goes to $.intermediateSigningKey.signedKey.keyValue
final PublicKey intermediatePubKey = intermediateKeyPair.getPublic();
// Generate ephemeral pub key. Used to create a shared secret to decrypt the signed message
final KeyPair ephemeralKeyPair = generateKeyPair();
final PrivateKey ephemeralPrivKey = ephemeralKeyPair.getPrivate();
// Convert to uncompressed point and B64 encode. Goes to $.signedMessage.ephemeralPublicKey
final PublicKey ephemeralPubKey = ephemeralKeyPair.getPublic();
final byte[] ephemeralPubKeyBytes = getUncompressedPoint(ephemeralPubKey);
//Parse merchant public key
final ECPublicKey merchantPublicKey = encryptRequest.getPublicKey();
/*
STEP 2: Sign the intermediate key. This step will generate the intermediateSigningKey block in the json
ECDSA(length_of_sender_id || sender_id || length_of_protocol_version || protocol_version || length_of_signed_key || signed_key)
*/
final long keyExpiry = Instant.now().plus(365, ChronoUnit.DAYS).toEpochMilli();
final String intermediatePubKeyB64 = java.util.Base64.getEncoder().encodeToString(intermediatePubKey.getEncoded());
response.setIntermediateSigningKey(keyExpiry, intermediatePubKeyB64);
final byte[] senderComponent = getSignatureBytesForComponent(SENDER_ID);
final byte[] protocolVersionComponent = getSignatureBytesForComponent(PROTOCOL_VERSION);
final String signingKeyJson = new Gson().toJson(response.getIntermediateSigningKey().getSignedKey());
final byte[] signingKeyComponent = getSignatureBytesForComponent(signingKeyJson);
//Assemble all components into one byte array
final byte[] intermediateSignatureComponentBytes = ByteBuffer
.allocate(senderComponent.length + protocolVersionComponent.length + signingKeyComponent.length)
.put(senderComponent)
.put(protocolVersionComponent)
.put(signingKeyComponent)
.array();
//Sign the byte array using ECDSA
final String intermediateSignature = ecdsaSignMessageB64(rootPrivKey, intermediateSignatureComponentBytes);
response.setIntermediateSigningKeySignatures(intermediateSignature);
/*
Step 3: Create the signed message. Includes tag, encrypted data and ephemeralPubKey
*/
//Step 3a: Generate shared secret. Used for symmetric encryption
final byte[] sharedSecret = EllipticCurves.computeSharedSecret(
(ECPrivateKey) ephemeralPrivKey,
merchantPublicKey.getW());
//Step 3b: Generate HMAC key and symmetric key using shared secret
final byte[] eciesKey = Hkdf.computeEciesHkdfSymmetricKey(
ephemeralPubKeyBytes,
sharedSecret,
HMAC_SHA256_ALGO,
HKDF_EMPTY_SALT,
SENDER_ID.getBytes(StandardCharsets.UTF_8),
HMAC_SHA256_ALGO_SIZE + AES_CTR_ALGO_SIZE //256 bit aes key and 256 bit hmac key
);
final byte[] aesKey = Arrays.copyOf(eciesKey, AES_CTR_ALGO_SIZE); // [0,32] bytes
final byte[] hmacKey = Arrays.copyOfRange(eciesKey, HMAC_SHA256_ALGO_SIZE, HMAC_SHA256_ALGO_SIZE * 2); //[32,64]
//Step 3c: Encrypt data
final Cipher cipher = EngineFactory.CIPHER.getInstance(AES_CTR_ALGO);
cipher.init(
Cipher.ENCRYPT_MODE,
new SecretKeySpec(aesKey, "AES"),
new IvParameterSpec(AES_CTR_ZERO_IV));
final byte[] cipherBytes = cipher.doFinal(encryptRequest.getMessage().getBytes(StandardCharsets.UTF_8));
//Step 3d: Compute HMAC tag
final SecretKeySpec secretKeySpec = new SecretKeySpec(hmacKey, HMAC_SHA256_ALGO);
final Mac mac = EngineFactory.MAC.getInstance(HMAC_SHA256_ALGO);
mac.init(secretKeySpec);
final byte[] hmacBytes = mac.doFinal(cipherBytes);
//Step 3e: Populate data in response. $.signedMessage
final String ephemeralPublicKeyB64 = java.util.Base64.getEncoder().encodeToString(ephemeralPubKeyBytes);
final String hmacB64 = java.util.Base64.getEncoder().encodeToString(hmacBytes);
final String cipherB64 = java.util.Base64.getEncoder().encodeToString(cipherBytes);
response.setSignedMessage(cipherB64, hmacB64, ephemeralPublicKeyB64);
/*
Step 4: Create $.signature using intermediate priv and the serialized signed key
ECDSA(length_of_sender_id || sender_id || length_of_recipient_id || recipient_id || length_of_protocolVersion || protocolVersion || length_of_signedMessage || signedMessage)
*/
final String merchantIdComponentString = getMerchantIdComponent(encryptRequest.getMerchantId());
final byte[] merchantComponent = getSignatureBytesForComponent(merchantIdComponentString);
;
final String signedMessageJson = new Gson().toJson(response.getSignedMessage());
final byte[] signedMessageComponent = getSignatureBytesForComponent(signedMessageJson);
//Assemble all components into one byte array
final int signatureComponentLength = senderComponent.length + merchantComponent.length + protocolVersionComponent.length + signedMessageComponent.length;
final byte[] signatureComponentBytes = ByteBuffer
.allocate(signatureComponentLength)
.put(senderComponent)
.put(merchantComponent)
.put(protocolVersionComponent)
.put(signedMessageComponent)
.array();
final String signatureB64 = ecdsaSignMessageB64(intermediatePrivKey, signatureComponentBytes);
response.setSignature(signatureB64);
} catch (final Exception e) {
throw new Exception("Encryption failed: " + e);
}
return response;
}
static byte[] getSignatureBytesForComponent(final String component) {
final byte[] componentLengthBytes = ByteBuffer
.allocate(4)
.order(ByteOrder.LITTLE_ENDIAN)
.putInt(component.length())
.array();
final byte[] componentBytes = ByteBuffer
.wrap(component.getBytes(StandardCharsets.UTF_8))
.array();
return ByteBuffer.allocate(componentLengthBytes.length + componentBytes.length)
.put(componentLengthBytes)
.put(componentBytes)
.array();
}
public static KeyPair generateKeyPair() throws Exception {
try {
final KeyPairGenerator kpg = KeyPairGenerator.getInstance(ASYMMETRIC_KEY_TYPE, SECURITY_PROVIDER);
kpg.initialize(generateECParameterSpec());
return kpg.generateKeyPair();
} catch (final GeneralSecurityException e) {
System.out.println("Failed to generate ECC KeyPair. " + e);
throw new Exception("Failed to generate ECC KeyPair.");
}
}
protected static ECNamedCurveSpec generateECParameterSpec() {
final ECNamedCurveParameterSpec bcParams = ECNamedCurveTable.getParameterSpec(EC_CURVE);
final ECNamedCurveSpec params = new ECNamedCurveSpec(bcParams.getName(), bcParams.getCurve(),
bcParams.getG(), bcParams.getN(), bcParams.getH(), bcParams.getSeed());
return params;
}
private static String ecdsaSignMessageB64(final PrivateKey privateKey, final byte[] messageToSign) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
final Signature intermediateECDSASignature = Signature.getInstance(SIGNATURE_ALGORITHM);
intermediateECDSASignature.initSign(privateKey);
intermediateECDSASignature.update(messageToSign);
final byte[] intermediateSignatureBytes = intermediateECDSASignature.sign();
return java.util.Base64.getEncoder().encodeToString(intermediateSignatureBytes);
}
public static byte[] getUncompressedPoint(final PublicKey publicKey) {
final java.security.interfaces.ECPublicKey pub = (java.security.interfaces.ECPublicKey) publicKey;
return pub.getEncoded();
}
#Component
public class ProfileManager {
#Autowired
private Environment environment;
#PostConstruct
public void setup() {
System.out.println(this.getActiveProfiles());
}
public boolean isProfileActive(final String profile) {
for (final String activeProfile : this.environment.getActiveProfiles()) {
if (activeProfile.equalsIgnoreCase(profile)) {
return true;
}
}
return false;
}
public List<String> getActiveProfiles() {
return List.of(this.environment.getActiveProfiles());
}
}
#Slf4j
#Data
#NoArgsConstructor
public class GoogleEncryptResponse implements EncryptResponse {
//These entities represent the structure provided by the GPay spec. See link in class docs
private String protocolVersion = "ECv2";
private String signature;
private IntermediateSigningKey intermediateSigningKey = new IntermediateSigningKey();
private SignedMessage signedMessage = new SignedMessage();
public void setIntermediateSigningKey(final long keyExpiration, final String keyValue) {
this.intermediateSigningKey.getSignedKey().setKeyExpiration(Long.toString(keyExpiration));
this.intermediateSigningKey.getSignedKey().setKeyValue(keyValue);
}
public void setIntermediateSigningKeySignatures(final String... signatures) {
this.intermediateSigningKey.setSignatures(signatures);
}
public void setSignedMessage(final String encryptedMessage, final String tag, final String ephemeralPublicKey) {
this.signedMessage.setEncryptedMessage(encryptedMessage);
this.signedMessage.setTag(tag);
this.signedMessage.setEphemeralPublicKey(ephemeralPublicKey);
}
#Override
public String getResult() {
Gson gson = new Gson();
JsonObject intermediateSigningKeyJson = new JsonObject();
JsonArray intermediateSigningKeyJsonSignatures = new JsonArray();
Arrays.stream(intermediateSigningKey.getSignatures())
.forEach(intermediateSigningKeyJsonSignatures::add);
intermediateSigningKeyJson.add("signatures", intermediateSigningKeyJsonSignatures);
intermediateSigningKeyJson.addProperty("signedKey", gson.toJson(getIntermediateSigningKey().getSignedKey()));
JsonObject payloadRoot = new JsonObject();
payloadRoot.addProperty("protocolVersion", protocolVersion);
payloadRoot.addProperty("signature", signature);
payloadRoot.add("intermediateSigningKey", intermediateSigningKeyJson);
payloadRoot.addProperty("signedMessage", gson.toJson(getSignedMessage()));
return getJsonElementHtmlSafe(payloadRoot);
}
#Data
#NoArgsConstructor
public static class IntermediateSigningKey {
private SignedKey signedKey = new SignedKey();
/**
* Signatures signed by the verifying key. Should be B64 encoded used SHA256 hashing with
* ECDSA
*/
private String[] signatures;
#Data
#NoArgsConstructor
static class SignedKey {
/**
* A Base64 version of key encoded in ASN.1 type.
* aka, der format. key.getEncoded()
*/
private String keyValue;
/**
* Date and time when the intermediate key expires as UTC milliseconds since epoch
*/
private String keyExpiration;
}
}
#Setter
#NoArgsConstructor
private static class SignedMessage {
/**
* A Base64-encoded encrypted message that contains payment information
*/
private String encryptedMessage;
/**
* A Base64-encoded ephemeral public key associated
* with the private key to encrypt the message in uncompressed point format
*/
private String ephemeralPublicKey;
/**
* A Base64-encoded MAC of encryptedMessage.
*/
private String tag;
}
}
public interface EncryptResponse {
String getResult();
}
public interface EncryptRequest<K extends PublicKey> {
/**
* Public key used to encrypt
* #return
*/
K getPublicKey();
void setPublicKey(K publicKey);
String getAlgorithm();
/**
* Set the PublicKey object using a base64 encoded key.
* #param base64EncodedKey
*/
default void setPublicKey(final String base64EncodedKey) throws Exception {
PublicKey publicKey = CryptoUtility.getPublicFromBase64(base64EncodedKey, getAlgorithm());
setPublicKey((K) publicKey);
}
/**
* Message to encrypt
* #return
*/
String getMessage();
}
public static String getJsonElementHtmlSafe(JsonElement element) {
try {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setLenient(false);
jsonWriter.setHtmlSafe(true); //Ensures '=' will appear as \u003d
Streams.write(element, jsonWriter);
return stringWriter.toString();
} catch (IOException e) {
return null;
}
}
#Builder
#Data
#NoArgsConstructor
#AllArgsConstructor
public static class GoogleDecryptRequest implements DecryptRequest<ECPrivateKey> {
/**
* This represents the encrypted value
*/
private String encryptedMessage;
/**
* The current active private key
*/
private ECPrivateKey privateKey;
/**
* These privateKey represents a private key that may have been rotated recently.
* May be empty
*/
#Singular
private List<ECPrivateKey> oldPrivateKeys = new ArrayList<>();
/**
* Corresponds to the GPay console merchant id
* Value must be prefixed with merchant:<br/>
* For example: merchant:1234345345345
*/
private String merchantId;
private String algorithm = "EC";
public void addOldPrivateKey(final ECPrivateKey privateKey) {
this.oldPrivateKeys.add(privateKey);
}
public static class GoogleDecryptRequestBuilder {
public GoogleDecryptRequestBuilder encryptedMessage(final String encryptedMessage) {
try {
final JsonElement jsonPayload = new JsonParser().parse(encryptedMessage);
this.encryptedMessage = getJsonElementHtmlSafe(jsonPayload);
} catch (final Exception e) {
System.out.println("Failed to parse GooglePay encrypted message. Data MUST be a valid json string." + e);
}
return this;
}
}
}
public interface DecryptRequest<K extends PrivateKey> {
/**
* Return the message that will be decrypted.
* #return
*/
String getEncryptedMessage();
/**
* Return the private key required for decryption
* #return
*/
K getPrivateKey();
void setPrivateKey(K privateKey);
String getAlgorithm();
/**
* Set the PublicKey object using a base64 encoded key.
* #param base64EncodedKey
*/
default void setPrivateKey(final String base64EncodedKey) throws Exception {
PrivateKey privateKey = CryptoUtility.getPrivateFromPKCS8Base64(base64EncodedKey, getAlgorithm());
setPrivateKey((K) privateKey);
}
}
}
CryptoUtility class:
package com.decryption.test;
public class CryptoUtility {
public static PrivateKey getPrivateFromPKCS8Base64(final String base64, final String algorithm) throws Exception {
final byte[] privateKeyBytes = Base64.decode(base64);
return getPrivateFromPKCS8Der(privateKeyBytes, algorithm);
}
public static PrivateKey getPrivateFromPKCS8Der(final byte[] privateKeyBytes, final String algorithm) throws Exception {
try {
final PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKeyBytes);
final KeyFactory kf = KeyFactory.getInstance(algorithm, CryptoLib.SECURITY_PROVIDER);
return kf.generatePrivate(spec);
} catch (final Exception e) {
throw new Exception("Failed to created Private Key." + e);
}
}
public static PublicKey getPublicFromBase64(final String base64, final String algorithm) throws Exception {
final byte[] publicKeyBytes = Base64.decode(base64);
return getPublicFromDer(publicKeyBytes, algorithm);
}
public static PublicKey getPublicFromDer(final byte[] publicKeyBytes, final String algorithm) throws Exception {
try {
final X509EncodedKeySpec spec = new X509EncodedKeySpec(publicKeyBytes, algorithm);
final KeyFactory kf = KeyFactory.getInstance(algorithm, CryptoLib.SECURITY_PROVIDER);
return kf.generatePublic(spec);
} catch (final Exception e) {
throw new Exception("Failed to created Public Key from string value" + e);
}
}
}
JUnit class:
package com.decryption.test;
#ExtendWith(MockitoExtension.class)
public class DecryptionTest {
#InjectMocks
private CryptoLib googleECCCryptoLibrary;
#Mock
private CryptoLib.ProfileManager profileManager;
#Test
void encryptAndDecryptMockData() throws Exception {
final ECPrivateKey merchantPrivKey = (ECPrivateKey) CryptoUtility.getPrivateFromPKCS8Base64("MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgsfylDy+Q3lfR6nbipZGDZWzp6P+qiQeApJizxG/hj96gCgYIKoZIzj0DAQehRANCAAQg1SVNuof6FndzJkbPst37moW+L/36EPLiiosS5BBSsLK4q2aLxzk2M732OpDHkXTp31ZQitPDQImndaY57ZSM", "EC");
final ECPublicKey merchantPubKey = (ECPublicKey) CryptoUtility.getPublicFromBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEINUlTbqH+hZ3cyZGz7Ld+5qFvi/9+hDy4oqLEuQQUrCyuKtmi8c5NjO99jqQx5F06d9WUIrTw0CJp3WmOe2UjA==", "EC");
final CryptoLib.GoogleEncryptRequest encRequest = CryptoLib.GoogleEncryptRequest.builder()
.merchantId("12345678901234567890")
.message("heyhey!")
.publicKey(merchantPubKey)
.build();
final CryptoLib.EncryptResponse encResponse = googleECCCryptoLibrary.encrypt(encRequest);
final CryptoLib.GoogleDecryptRequest decryptRequest = CryptoLib.GoogleDecryptRequest.builder()
.privateKey(merchantPrivKey)
.merchantId("12345678901234567890")
.encryptedMessage(encResponse.getResult())
.build();
given(this.profileManager.isProfileActive(anyString())).willReturn(true);
final String decResponse = this.googleECCCryptoLibrary.decrypt(decryptRequest);
assertEquals(encRequest.getMessage(), decResponse);
}
}
Stacktrace:
Caused by: java.security.GeneralSecurityException: java.lang.IllegalStateException: java.security.InvalidAlgorithmParameterException: Curve not supported: secp256r1 [NIST P-256,X9.62 prime256v1] (1.2.840.10045.3.1.7)
at com.google.crypto.tink.subtle.EllipticCurves.computeSharedSecret(EllipticCurves.java:963)
ECDHKeyAgreement.engineGenerateSecret() implementation seems to be changed in Java 16 where it is now throwing:
throw new IllegalStateException(new InvalidAlgorithmParameterException("Curve not supported
as compared to the implementation in Java 13.
Any recommendations to implement encryption in JDK 16 with BouncyCastle as the provider or if it should be replaced by any other provider?

iText7 deferred signed pdf document shows “the document has been altered or corrupted since the signature was applied”

I checked with other similar issues on Stackoverflow, but it doesn’t work on my case.
Situation: I am developing an application which needs to sign the pdf document. The signing key is held by another company, let’s say it’s CompanyA.
I did the following steps:
Got the pdf document to sign ready.
Created a Temp pdf file which added an Empty Signature in the original pdf.
Read the Temp pdf to get the message digest. (Encode it to base64)
Send the message digest (Base64 encoded) to the CompanyA to get signed.
Get the signed digest (base64 encoded) from CompanyA.
Do the base64 decoding. And embedded the result into the Temp pdf to get the final signed pdf.
Everything goes well and I can get the final signed pdf. But When I open it in Adobe reader, it says “the document has been altered or corrupted since the signature was applied”.
I used this getHashBase64Str2Sign to get the message digest (in base64). This method calls the emptySignature() method to create the Temp file with empty signature, and then calls the getSignatureHash() method to read the Temp file to get the message digest.
public static String getHashBase64Str2Sign() {
try {
// Add BC provider
BouncyCastleProvider providerBC = new BouncyCastleProvider();
Security.addProvider(providerBC);
// Create parent path of dest pdf file, if not exist
File file = new File(DEST).getParentFile();
if (!file.exists()) {
file.mkdirs();
}
CertificateFactory factory = CertificateFactory.getInstance("X.509");
Certificate[] chain = new Certificate[1];
try (InputStream certIs = new FileInputStream(CERT)) {
chain[0] = factory.generateCertificate(certIs);
}
// Get byte[] hash
DeferredSigning app = new DeferredSigning();
app.emptySignature(SRC, TEMP, "sig", chain);
byte[] sh = app.getSignatureHash(TEMP, "SHA256", chain);
// Encode byte[] hash to base64 String and return
return Base64.getEncoder().encodeToString(sh);
} catch (IOException | GeneralSecurityException e) {
e.printStackTrace();
return null;
}
}
private void emptySignature(String src, String dest, String fieldname, Certificate[] chain)
throws IOException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
PdfSigner signer = new PdfSigner(reader, new FileOutputStream(dest), new StampingProperties());
PdfSignatureAppearance appearance = signer.getSignatureAppearance();
appearance.setPageRect(new Rectangle(100, 500, 200, 100));
appearance.setPageNumber(1);
appearance.setCertificate(chain[0]);
appearance.setReason("For test");
appearance.setLocation("HKSAR");
signer.setFieldName(fieldname);
/*
* ExternalBlankSignatureContainer constructor will create the PdfDictionary for
* the signature information and will insert the /Filter and /SubFilter values
* into this dictionary. It will leave just a blank placeholder for the
* signature that is to be inserted later.
*/
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.Adobe_PPKLite,
PdfName.Adbe_pkcs7_detached);
// Sign the document using an external container.
// 8192 is the size of the empty signature placeholder.
signer.signExternalContainer(external, 100000);
}
private byte[] getSignatureHash(String src, String hashAlgorithm, Certificate[] chain)
throws IOException, GeneralSecurityException {
InputStream is = new FileInputStream(src);
// Get the hash
BouncyCastleDigest digest = new BouncyCastleDigest();
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
byte hash[] = DigestAlgorithms.digest(is, digest.getMessageDigest(sgn.getHashAlgorithm()));
return sgn.getAuthenticatedAttributeBytes(hash, PdfSigner.CryptoStandard.CMS, null, null);
}
private void createSignature(String src, String dest, String fieldName, byte[] sig)
throws IOException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
try (FileOutputStream os = new FileOutputStream(dest)) {
PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());
IExternalSignatureContainer external = new MyExternalSignatureContainer(sig);
// Signs a PDF where space was already reserved. The field must cover the whole
// document.
PdfSigner.signDeferred(signer.getDocument(), fieldName, os, external);
}
}
Then, the message digest is sent to CompanyA for signing. After I got the signed digest from CompanyA (which is base64 encoded), I call the embedSignedHashToPdf() method to get the signed pdf document.
public static void embedSignedHashToPdf(String signedHash) {
try {
byte[] sig = Base64.getDecoder().decode(signedHash);
// Get byte[] hash
DeferredSigning app = new DeferredSigning();
app.createSignature(TEMP, DEST, "sig", sig);
} catch (IOException | GeneralSecurityException e) {
e.printStackTrace();
}
}
class MyExternalSignatureContainer implements IExternalSignatureContainer {
protected byte[] sig;
public MyExternalSignatureContainer(byte[] sig) {
this.sig = sig;
}
#Override
public void modifySigningDictionary(PdfDictionary signDic) {
}
#Override
public byte[] sign(InputStream arg0) throws GeneralSecurityException {
return sig;
}
}
At last, I can get the signed pdf document, but it shows error in Adobe Reader, like this:
Please check the original pdf, temp pdf, and the final signed pdf file as follows:
Original pdf - helloworld.pdf
Temp pdf - helloworld_empty_signed.pdf
Final pdf - helloworld_signed_ok.pdf
Ok, I see a number of issues in your code:
You determine the hash of the wrong bytes
In getSignatureHash with src containing the path of the intermediary PDF prepared for signing you do
InputStream is = new FileInputStream(src);
...
byte hash[] = DigestAlgorithms.digest(is, ...);
I.e. you calculate the hash value of the whole intermediary PDF.
This is incorrect!
The hash must be calculated for the PDF except the placeholder for the signature container which shall later be embedded:
The easiest way to hash that range, is to already calculate the hash in emptySignature and return it from there by using a hash-calculating IExternalSignatureContainer implementation instead of the dumb ExternalBlankSignatureContainer.
For example use this implementation:
public class PreSignatureContainer implements IExternalSignatureContainer {
private PdfDictionary sigDic;
private byte hash[];
public PreSignatureContainer(PdfName filter, PdfName subFilter) {
sigDic = new PdfDictionary();
sigDic.put(PdfName.Filter, filter);
sigDic.put(PdfName.SubFilter, subFilter);
}
#Override
public byte[] sign(InputStream data) throws GeneralSecurityException {
String hashAlgorithm = "SHA256";
BouncyCastleDigest digest = new BouncyCastleDigest();
try {
this.hash = DigestAlgorithms.digest(data, digest.getMessageDigest(hashAlgorithm));
} catch (IOException e) {
throw new GeneralSecurityException("PreSignatureContainer signing exception", e);
}
return new byte[0];
}
#Override
public void modifySigningDictionary(PdfDictionary signDic) {
signDic.putAll(sigDic);
}
public byte[] getHash() {
return hash;
}
}
like this:
PreSignatureContainer external = new PreSignatureContainer(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
signer.signExternalContainer(external, 16000);
byte[] documentHash = external.getHash();
You process the hash as if your CompanyA only returned naked signature bytes but you embed the bytes returned by CompanyA as if they were a full CMS signature container
In getSignatureHash you eventually don't return the alleged document hash but instead start constructing a CMS signature container and return its signed attributes:
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
...
return sgn.getAuthenticatedAttributeBytes(hash, PdfSigner.CryptoStandard.CMS, null, null);
Calculating PdfPKCS7.getAuthenticatedAttributeBytes(...) would only make sense if you then retrieved naked signature bytes for those attribute bytes and created a CMS signature container using the same PdfPKCS7 object:
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, sigtype, ocspList, crlBytes);
byte[] extSignature = RETRIEVE_NAKED_SIGNATURE_BYTES_FOR(sh);
sgn.setExternalDigest(extSignature, null, ENCRYPTION_ALGORITHM_USED_FOR_SIGNING);
byte[] encodedSig = sgn.getEncodedPKCS7(hash, sigtype, tsaClient, ocspList, crlBytes);
In particular your approach of calculating the signed attributes and then forgetting the PdfPKCS7 object does never make any sense at all.
But it actually looks like your CompanyA can return full CMS signature containers, not merely naked signature bytes as you immediately embed the returned bytes in embedSignedHashToPdf and createSignature and your example PDF does contain a full CMS signature container.
In such a case you don't need to use PdfPKCS7 at all but directly send the pre-calculated document digest to CompanyA to sign.
Thus, most likely you don't need PdfPKCS7 at all but instead send the document hash determined as explained above to CompanyA and embed their returned signature container.

Digital Signature different everytime

Digital signatures depend upon message and the secret key. Suppose receiver copies the message several times ( the reason for which we unique ids) , why will the signature be different (since the message and secret key are same)?
Bitcoin uses ECDSA, and the reason the signature is different for the same message and key is the random nonce, which is generated each time a signature is created.
An ECDSA signature is a pair (r,s) where r is the X coordinate of kG, and s = (m+r*x)/k (where k=nonce, m=message hash, x=private key, G=curve generator).
See also #3 in https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm#Signature_generation_algorithm
This answer is just a practical supplement to the theoretical answer provided above. This snippet digitally signs inside the for-loop the same message over and over again using the same private key and each time, it is printing out different digital signatures. Each of these signatures are valid and will verify using the signer's public key.
import java.security.Security;
import java.security.Signature;
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.KeyPairGenerator;
import java.security.KeyPair;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.interfaces.ECPublicKey;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
class liptic
{
static String the_message = "how are you, little tiger?";
public static void main(String args[]) throws Exception
{
Security.addProvider(new BouncyCastleProvider());
SecureRandom random = new SecureRandom();
ECParameterSpec ecSpec
=
ECNamedCurveTable.getParameterSpec("secp256k1");
KeyPairGenerator g =
KeyPairGenerator.getInstance("ECDH", "BC");
g.initialize(ecSpec, random);
KeyPair keyPair = g.generateKeyPair();
byte[] bob_pubkey = Hex.decode( "040581E4AEEEB1CEA57094D1AD97B8C721509B6E5D3690C70BBB8EB2C5FE8040"
+ "FB2C9B0A77EA2AD05C5E8DB499F647BC9A8BE829961950D6F5A45952C097CCB0BC");
Signature signer = Signature.getInstance("SHA256withECDSA", "BC");
ECPublicKeySpec pubkSpec = new ECPublicKeySpec(
ecSpec.getCurve().decodePoint(bob_pubkey), ecSpec);
ECPublicKey publicKey
= (ECPublicKey)KeyFactory.getInstance("ECDSA", "BC")
.generatePublic(pubkSpec);
byte[] signature = null;
byte[] input = the_message.getBytes();
signer.initSign(keyPair.getPrivate());
signer.update(input);
for (int i = 0; i < 5; i++) {
signature = signer.sign();
System.out.println(o(signature));
}
}
public static String o(byte[] bytes)
{
int n = 0;
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X", b));
n++;
if (n % 128 == 0) {
sb.append("\n");
}
}
return sb.toString();
}
}
Here are the digital signatures:
3046022100889CC42C4BAA07FF33AB34CADD8BCB0A44E77031D4A5F5A9849840DF3AB63FDA0221009CA5C49FC0EBE9F839A0CFAB18CEC91C9169FF439C1E2DFD724D06E2DB9FE258
30460221009D30465EFD3676982CBE12B998D41D012322C255594D5037F156143AEC7E7305022100A77FE7DEB580837A1A5A5D1B74334C56E9F26BA1834EE3AC93ECEB01349A6F1C
3045022100DF4AF153D808A9199C18C97E689F1214E7F59C621D6ECBAADFE206B83707CA2802203E592D0152E79E14084183206F4B6DBC95D68FBA2D23F65884A3B68FA79A4E04
304502202E9FA22709308D08106F76CBB6278B3F485A3C706EDA3FFAF5CE744D4B90E9510221009DD2370863D6C1CE36D828FF9B98347905F2D0856052C4A30B25DD00575B8921
3045022100AA46FEA1A80498E481D46B17EFD7FBE6656641CD719AF1F5DC0C77ADD334729D0220471472117374E0284074EBC81172E6271CA9D86F54AFCE6E6CF6863814EBF824
This is using the Elliptic Curve Digital Signature Algorithm.

Objective C AES encrypt equal to java version

I have java code to encrypt a password,
public static String encryptToString(String content,String password) throws IOException {
return parseByte2HexStr(encrypt(content, password));
}
private static byte[] encrypt(String content, String password) {
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(password.getBytes());
kgen.init(128, secureRandom);
SecretKey secretKey = kgen.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
Cipher cipher = Cipher.getInstance("AES");
byte[] byteContent = content.getBytes("utf-8");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] result = cipher.doFinal(byteContent);
return result;
} catch (Exception e) {
log.error(e.getMessage(),e);
}
return null;
}
public static String parseByte2HexStr(byte buf[]) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < buf.length; i++) {
String hex = Integer.toHexString(buf[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
sb.append(hex.toUpperCase());
}
return sb.toString();
}
Now I need to encrypt/decrypt it with objective-c, I do a lot of search and none method would generate the same encrypted output.
What's the objective-c version code equal to java code?
test case: encryptToString("test","password") => DA180930496EC69BFEBA923B7311037A
I believe the answers to this question are what you are looking for: Any cocoa source code for AES encryption decryption?
I adapted a function somebody posted as answer: https://gist.github.com/4335132
To use:
NSString *content = #"test";
NSData *dataToEncrypt = [content dataUsingEncoding:NSUTF8StringEncoding];
NSData *data = [dataToEncrypt AES128EncryptWithKey:#"password"];
NSString *hex = [data hexString];
It's not exactly the same because your Java code does not use the password itself for encrypting but uses it to seed a random number generator. But I think that this should still work with what you are trying to do.

How to create HMACSHA256 api signature in Salesforce Apex

Can somebody help in creating HMACSHA256 api signature in apex using crypto class. Corresponding java code is given below :-
public static void main(String[] args) throws GeneralSecurityException, IOException {
String secretKey = "secretKey";
String salt = "0123456789";
String generateHmacSHA256Signature = generateHmacSHA256Signature(salt, secretKey);
System.out.println("Signature: " + generateHmacSHA256Signature);
String urlEncodedSign = URLEncoder.encode(generateHmacSHA256Signature, "UTF-8");
System.out.println("Url encoded value: " + urlEncodedSign);
}
public static String generateHmacSHA256Signature(String data, String key) throws GeneralSecurityException {
byte[] hmacData = null;
try {
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(secretKey);
hmacData = mac.doFinal(data.getBytes("UTF-8"));
return new Base64Encoder().encode(hmacData);
} catch (UnsupportedEncodingException e) {
throw new GeneralSecurityException(e);
}
}
Thanks in advance
http://boards.developerforce.com/t5/Apex-Code-Development/How-to-create-HMACSHA256-api-signature/td-p/551055
I think that'll do it for you?
Copied here for posterity (in case the link dies)
AKK's answer:
"Re: How to create HMACSHA256 api signature
‎12-28-2012 02:58 AM
Sorry for unformatted code, actually I was looking into how to format but didn't find anything in mozilla and when login through chrome editor appeared.
I got the signature right using below code maybe this helps someone :-
public void genrateSignature() {
String salt = String.valueOf(Crypto.getRandomInteger());
String secretKey = 'secret_key';
String signature = generateHmacSHA256Signature(salt, secretKey);
System.debug('Signature : '+signature);
}
private static String generateHmacSHA256Signature(String saltValue, String secretKeyValue) {
String algorithmName = 'HmacSHA256';
Blob hmacData = Crypto.generateMac(algorithmName, Blob.valueOf(saltValue), Blob.valueOf(secretKeyValue));
return EncodingUtil.base64Encode(hmacData);
}
Thanks"