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.
I am implementing addSiteAccount1 to the rest api. The PKI means the username and password must be ssh encoded using a public key file. I have the public key file and I can generate an encrypted binary stream (eg "f\xBDZ\x16\xF5\xE6\xC42 .....". How do I encode this to be POSTed to addSiteAccount1 please? The Yodlee RSA encryption utility seems to generate hex(?) but my hex-encoded stringencrypted_str.unpack('H*') gives me an error response: "Decryption failure for FieldInfo:FieldInfoSingle".
This can also happens if one of the value field you are sending is not encrypted. You should be using BouncyCastle in this case. Adding code here as well for reference-
public static String encrypt(String plainText) throws Exception{
Security.addProvider(new BouncyCastleProvider());
String pub = "-----BEGIN PUBLIC KEY-----"+
"\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtUS7ZJpnbcu8B+mfGrr0Gz6A23lS893mEFNnuR+frbtWDsoIHTfN4yhfbslkzsAMp3ENvM6Ic/0nHEvftrZxFXSrN7n3xZ+mdzOV/u8rqZoB7MEu6mZvdg3zfj7dGglq/fqlYxzHLlxDHjeCrY0dSD0ZAX1zCm3IZ0ufbMBqTrsSaHAuDlIXaQlJXmz3/Y+YfynJZXth/ats1gTBQhMIU9lWutMa4iKkeehn+P9ja4pC9NUlB9W4pojF2Qs+pY4kgTb9+SP8WjnhoSAmJMQGbYwY3HOZyfuOqAmdjoh9Y0LEZ3tq5NGD0b+T7L+P/FuIzvjYZYq6g/FaWaPcVrVLpwIDAQAB"+
"\n-----END PUBLIC KEY-----";
System.out.println(pub);
String strt= pub;
StringReader fileReader= new StringReader(strt);
PEMReader pemReader= new PEMReader(fileReader);
PublicKey pk= (PublicKey)pemReader.readObject();
Cipher c = Cipher.getInstance(RSA_ECB_PKCS5);
PublicKey publicKey = pk;
c.init(Cipher.ENCRYPT_MODE, transformKey(publicKey,
"RSA", new BouncyCastleProvider()));
byte[] encValue= new byte[0];
try {
encValue = c.doFinal(plainText.getBytes());
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
String encrypted = DatatypeConverter.printHexBinary(encValue);
System.out.println("Encrypted value: "+encrypted);
return encrypted;
}
I have an sha256 hash and my goal is to sign it with CoSign SOAP API. Could you provide me a Java example? The PDF Signing example is working like a charm, but it's not perfectly clear for me, how should I set the InputDocument for this task.
Thanks a lot!
The code example below demonstrates how to sign a hash value using CoSign Signature SOAP API. However, I would strongly recommend to use CoSign Signature API instead, which is richer in its functionality and is way more intuitive.
public static byte[] SAPIWS_SignHash(byte[] data, String username, String domain, String password) throws Exception
{
byte[] signedHash = null;
try {
// Set hash method & data
DocumentHash documentHash = new DocumentHash();
DigestMethodType digestMethod = new DigestMethodType();
digestMethod.setAlgorithm("http://www.w3.org/2001/04/xmlenc#sha256");
documentHash.setDigestMethod(digestMethod);
documentHash.setDigestValue(data);
// Set user credentials
ClaimedIdentity claimedIdentity = new ClaimedIdentity();
NameIdentifierType nameIdentifier = new NameIdentifierType();
nameIdentifier.setValue(username);
nameIdentifier.setNameQualifier(domain);
CoSignAuthDataType coSignAuthData = new CoSignAuthDataType();
coSignAuthData.setLogonPassword(password);
claimedIdentity.setName(nameIdentifier);
claimedIdentity.setSupportingInfo(coSignAuthData);
// Build complete request object
SignRequest signRequest = new SignRequest();
RequestBaseType.InputDocuments inputDocs = new RequestBaseType.InputDocuments();
inputDocs.getOtherOrTransformedDataOrDocument().add(documentHash);
RequestBaseType.OptionalInputs optionalParams = new RequestBaseType.OptionalInputs();
optionalParams.setSignatureType("urn:ietf:rfc:2437:RSASSA-PKCS1-v1_5");
optionalParams.setClaimedIdentity(claimedIdentity);
signRequest.setOptionalInputs(optionalParams);
signRequest.setInputDocuments(inputDocs);
// Initiate service client
DSS client = new DSS(new URL("https://prime.cosigntrial.com:8080/sapiws/dss.asmx"));
// Send the request
DssSignResult response = client.getDSSSoap().dssSign(signRequest);
// Check response output
if ("urn:oasis:names:tc:dss:1.0:resultmajor:Success".equals(response.getResult().getResultMajor())) {
// On success- return signature buffer
ResponseBaseType.OptionalOutputs doc = response.getOptionalOutputs();
signedHash = doc.getDocumentWithSignature().getDocument().getBase64Data().getValue();
}
else {
throw new Exception(response.getResult().getResultMessage().getValue());
}
}
catch (Exception e)
{
System.out.println("Error: " + e.getMessage());
e.printStackTrace();
}
return signedHash;
}
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.
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"