Problem signing PDF with iText, PKCS11 and SHA-256 Algorithm - itext

I'm trying to sign a pdf using PCKS11 (USB Token) and iText, It works fine when I use SHA-1 algorithm, the signature is valid and everything works fine however when I change it to SHA256 the signature is not valid, Does anyone know why? I'll put my code below:
PKCS11 pkcs11 = PKCS11.getInstance("C:\\Windows\\System32\\example.dll", "C_GetFunctionList", null,false);
long[] slotList = pkcs11.C_GetSlotList(true);
String providerString = "name=*\nlibrary=C:\\Windows\\System32\\example.dll\n" + "slot=" + slotList [0];
SunPKCS11 sunPKCS11 = new SunPKCS11(new ByteArrayInputStream(providerString .getBytes()));
Provider provider = sunPKCS11;
KeyStore keyStore = KeyStore.getInstance("PKCS11", provider);
keyStore.load(null, password);
keyStore.aliases();
Security.addProvider(keyStore.getProvider());
List<String> aliases = Collections.list(keyStore.aliases());
String alias = aliases.get(0);
PrivateKey pk = (PrivateKey)keyStore.getKey(alias,password);
Certificate[] certChain = new Certificate[1];
certChain[0] = signerCert;
PdfReader reader = new PdfReader(inputData);
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
PdfStamper stamper = PdfStamper.createSignature(reader, outStream, '\0',null,true);
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
String fieldName = keyStore.getCertificateAlias(signerCert).replaceAll(".","");
appearance.setVisibleSignature(new Rectangle(420, 10, 70, 85), pageNumber,fieldName);
ExternalSignature es = new PrivateKeySignature(pk, DigestAlgorithms.SHA256,
keyStore.getProvider().getName());
ExternalDigest digest = new BouncyCastleDigest();
MakeSignature.signDetached(appearance,
digest, es,
certChain, null,
null, null,
0, CryptoStandard.CADES);
return outStream.toByteArray();
Why does it work with SHA-1 and not SHA-2? Where do you think the problem arises?

There are mismatches and errors in your signature.
download.pdf
This is the example file you shared in a comment to your question
Mismatch of Document Digest
The SHA-256 hash value of the signed bytes of your PDF is
9356BCD36F172806A3DCE7F062A66441E7C1DDC9203ABDAA0154A3F19208C8E3
but the embedded signature container claims it to be
5F892978FF2459157D631809A05F5DBCFCB55800236D2D5C3E4E4D94577012B4
According to your code that should not happen. Are you sure that PDF has been created by the code in your question? Or have you somehow changed it as you indicated by your I changed my hash like this comment and created the example document thereafter? Then this discrepancy most likely is caused by your change.
Broken Raw RSA Signature
After decrypting the raw signature bytes and removing the padding one gets
3031300D0609608648016503040201050004201DC71B824BAA3C7EC6744A0941CFADDAA893E8C1
This is incomplete. It corresponds to
SEQUENCE (2 elem)
SEQUENCE (2 elem)
OBJECT IDENTIFIER 2.16.840.1.101.3.4.2.1 sha-256 (NIST Algorithm)
NULL
OCTET STRING (32 byte) 1DC71B824BAA3C7EC6744A0941CFADDAA893E8C1XXXXXXXXXXXXXXXXXXXXXXXX
with the 12 bytes for that XXXXXXXXXXXXXXXXXXXXXXXX piece missing.
Thus, it looks like the signing device does not really support SHA256withRSA, at least not in combination with the example.dll PKCS#11 driver and the SunPKCS11 security provider.
As an aside, the OCTET STRING there should have been
EC7FCC5D003DFEC58B0ECB49CEEAD28495FFA8D798A1A88DA6051C1857B971EC
Thus, it looks like here is another mismatch, the 1DC71B824BAA3C7EC6744A0941CFADDAA893E8C1 actually there appears not to be related to this value.
SHA256.pdf
Can you check out this one? this one is made with itext's library and no code was changed.
In this file there indeed are no hash mismatches anymore, merely the signature value is cut off, here
3031300D0609608648016503040201050004204C8440B547E6A0EFD1489B8F5B5DFDA2DFA45DC1
corresponding to
SEQUENCE (2 elem)
SEQUENCE (2 elem)
OBJECT IDENTIFIER 2.16.840.1.101.3.4.2.1 sha-256 (NIST Algorithm)
NULL
OCTET STRING (32 byte) 4C8440B547E6A0EFD1489B8F5B5DFDA2DFA45DC1XXXXXXXXXXXXXXXXXXXXXXXX
The complete OCTET STRING should have been
4C8440B547E6A0EFD1489B8F5B5DFDA2DFA45DC19048B5E53D468FE6A8E4E973
So apparently indeed the chain of your signing device, the example.dll PKCS#11 driver, and the SunPKCS11 security provider from your JRE does not support SHA256withRSA.

Related

ECDSA signature generated with mbedtls not verifiable in JOSE (while code worked with RSA key)

I have a small application running on an ESP32 dev board (I use the Arduino IDE together with the shipped mbedtls) that issues and verifies JWT tokens. I have at first used RSA signatures successfully, but now wanted to go for shorter signatures and thus attempted to use ECDSA.
The application itself can issue tokens and verify them as well, but if I attempt to verify the tokens outside of my application - for instance with JOSE or the Debugger- I get verification failures and I cant quite wrap my head around why this is happening.
This is an example token (this token does practically not contain information):
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NzI0MTMxNjgsImV4cCI6MTY3MjQxNjc2OH0.MEUCIAjwEDXI424qjrAkSzZ_ydcVLOSAvfQ8YVddYvzDzMvQAiEAkVy4d-hZ01KpcMNKhPHk8E_SDYiB4JKwhm-Kc-Z81rI
This is the corresponding public key (this key is not used anywhere besides the purpose of presenting the issue here):
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUGnNIOhPhZZSOg4A4BqAFtGO13W4BGDQpQ0ieTvLU9/CXrY7W77o7pNx7tvugeIoYJxS0NjmxvT4TMpo4Z8P7A==
-----END PUBLIC KEY-----
As far as I understand, JWT tokens can be issued and verified using ECDSA. The so called "ES256" method is supposed to use prime256v1 in combination with SHA256, so I generated my key material with the following commands:
openssl ecparam -name prime256v1 -genkey -noout -out ecc-private.pem
openssl ec -in ecc-private.pem -pubout -out ecc-public.pem
For the signing part, the private key is loaded as follows, where ecc_priv is a String containing the PEM representation of the key:
//get the key
byte *keybuffer = (byte*)malloc((ecc_priv.length()+1)*sizeof(byte));
ecc_priv.getBytes(keybuffer, ecc_priv.length() + 1);
mbedtls_pk_context pk_context;
mbedtls_pk_init(&pk_context);
int rc = mbedtls_pk_parse_key(&pk_context, keybuffer, ecc_priv.length() + 1, NULL, 0);
if (rc != 0){
printf("Failed to mbedtls_pk_parse_key: %d (-0x%x): %s\n", rc, -rc, mbedtlsError(rc));
return -1;
}
free(keybuffer);
Since this worked for me with RSA keys, I just replaced the keys and kept all other code to sign the actual message. As far as I understand, this should be possible with mbedtls_pk methods:
//mbedtls context
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_entropy_init(&entropy);
const char* pers="some entropy";
mbedtls_ctr_drbg_seed(
&ctr_drbg,
mbedtls_entropy_func,
&entropy,
(const unsigned char*)pers,
strlen(pers));
//get the header and payload bytes
byte *headerAndPayloadbytes = (byte*)malloc((headerAndPayload.length()+1)*sizeof(byte));
headerAndPayload.getBytes(headerAndPayloadbytes, headerAndPayload.length() + 1);
//prepare digest
uint8_t digest[32];
rc = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), headerAndPayloadbytes, headerAndPayload.length(), digest);
if (rc != 0) {
printf("Failed to mbedtls_md: %d (-0x%x): %s\n", rc, -rc, mbedtlsError(rc));
return -1;
}
free(headerAndPayloadbytes);
//prepare output
byte *oBuf = (byte*)malloc(5000*sizeof(byte));
size_t retSize;
//sign digest
rc = mbedtls_pk_sign(&pk_context, MBEDTLS_MD_SHA256, digest, sizeof(digest), oBuf, &retSize, mbedtls_ctr_drbg_random, &ctr_drbg);
if (rc != 0) {
printf("Failed to mbedtls_pk_sign: %d (-0x%x): %s\n", rc, -rc, mbedtlsError(rc));
return -1;
}
//encode signature to base64
unsigned int osize = encode_base64_length(retSize);
byte *output = (byte*)malloc((osize+1)*sizeof(byte));
encode_base64(oBuf, retSize, output);
String sig = String((char*)output);
free(output);
//base64 URL specific
sig.replace('+','-');
sig.replace('/','_');
sig.replace("=","");
String completejwt = headerAndPayload + "." + sig;
//free resources
mbedtls_ctr_drbg_free( &ctr_drbg );
mbedtls_entropy_free( &entropy );
mbedtls_pk_free(&pk_context);
free(oBuf);
My expectation was that I can simply replace the RSA keys with the ECDSA (prime256v1) keys and keep everything else as is, but the resulting token are not verifiable outside of my application. Again I want to emphasize that inside my application I can definitely verify the token and that the code worked perfectly fine with RSA keys, even outside my application. There must be something Im missing here, Im sure about that.
Any help or directions to research are highly appreciated.
EDIT: Here is a minimal compilable example (Arduino sketch)
Your ECDSA signature is a DER-encoded ASN.1 structure, rather than a simple r || s concatenation as proposed by IEEE-P1363 which is what the JOSE specification mandates.

Determining attributes of flutter encrypt.dart

I am using encrypt.dart to AES encrypt a string ("text") based on a 32 digit password ("password") as follows:
encryptPass(String text, String password) {
final key = getKey(password);
final iv = encrypt.IV.fromLength(16);
final encrypter = encrypt.Encrypter(encrypt.AES(key)); //Uses AES/SIC/PKCS7
final e = encrypter.encrypt(text, iv: iv);
String encryptedString = e.base64.toString();
return encryptedString;
}
getKey(String masterPass) {
String keyString = masterPass;
if (keyString.length < 32) {
int count = 32 - keyString.length;
for (var i = 0; i < count; i++) {
keyString += ".";
}
}
final keyReturn = encrypt.Key.fromUtf8(keyString);
return keyReturn;
}
Side note: This works, but it produces the same value every time for a given input string, even though my "iv" and "salt" are supposedly random. How does this happen?
MAIN PROBLEM: I am trying to recreate this process using spongy castle in kotlin. The problem is that I don't know certain important attributes of the encrypt.dart AES functions. What values are used for:
salt length: 16, 32, 128, 256?? ("desiredKeyLength" var in encrypted.dart. not specified anywhere)
iteration count: (I think this is 100, but I am not certain.)
Secret Key algorithm: I assumed PBKDF2WithHmacSHA1 based on "final pbkdf2" of encrypted.dart.
key length: ?
Here is my current attempt at spongy castle implementation for reference:
fun encryptAESBasic(input: String, password: String): String {
Security.insertProviderAt(org.spongycastle.jce.provider.BouncyCastleProvider(), 1)
val masterpw = password.toCharArray()
val random = SecureRandom()
val salt = ByteArray(256)
random.nextBytes(salt)
val factory: SecretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
val spec: KeySpec = PBEKeySpec(masterpw, salt, 100, 128)
val tmp: SecretKey = factory.generateSecret(spec)
val key: SecretKey = tmp
val cipher = Cipher.getInstance("AES/SIC/PKCS7PADDING", "SC")
val iv = ByteArray(16)
SecureRandom().nextBytes(iv)
cipher.init(Cipher.ENCRYPT_MODE, key, IvParameterSpec(iv))
val cipherText: ByteArray = cipher.doFinal(input.toByteArray())
return cipherText.toString()
}
The Dart code uses a zero IV (an IV consisting of only 0x00 values), which is why always the same ciphertext is generated.
As you already figured out, the Dart code applies the SIC mode and PKCS7 padding by default. The SIC mode is another name for the CTR mode, which is a stream cipher mode and therefore does not require any padding. The PKCS7 padding used in the Dart code is therefore unnecessary.
Note that using CTR mode in conjunction with a static IV (such as a zero IV) is a fatal bug and in general extremely insecure (s. here).
As key derivation, the Dart code pads the password with . until the key size is 32 bytes, which is required for AES-256. This key derivation is also very insecure. When using a password, a reliable key derivation function such as PBKDF2 should always be used (as in the Kotlin Code).
The Dart code should therefore be revised and made more secure before porting to Kotlin. This requires the following changes:
A random IV is to be generated for each encryption.
PKCS7 padding should be disabled.
The code does not check the authenticity/integrity of the ciphertext. An additional authentication tag (MAC) must be applied for this purpose. It is recommended to switch from CTR to GCM mode, which is based on CTR mode but includes data authenticity/integrity in addition to confidentiality (authenticated encryption) and generates the tag implicitly.
A secure key derivation (e.g. PBKDF2, see Kotlin code) must be used. In combination with this, a random salt is to be generated for each key derivation (s. also the other answer).
Salt and IV (both not secret), as well as the tag are to be concatenated with the ciphertext (salt|IV|ciphertext|tag). Note that for GCM, many libraries perform concatenation of ciphertext and tag implicitly.
Of course - from a technical point of view - the Dart code can be ported to Kotlin, e.g.
fun encryptPass(text: String, password: String): String {
val secretKeySpec = SecretKeySpec(getKey(password), "AES") // Apply a reliable key derivation function (getKey() is insecure)
val cipher = Cipher.getInstance("AES/CTR/PKCS5PADDING") // Disable padding (CTR doesn't require padding)
val iv = ByteArray(16) // Generate random IV (CTR with static IV is extremely insecure)
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, IvParameterSpec(iv))
val cipherText: ByteArray = cipher.doFinal(text.toByteArray(Charset.forName("UTF-8"))) // Authenticity/integrity missing
return Base64.encodeToString(cipherText, Base64.DEFAULT); // Concatenation of salt, IV, ciphertext and authentication tag missing
}
fun getKey(masterPass: String): ByteArray {
return masterPass.padEnd(32, '.').toByteArray(Charset.forName("UTF-8"))
}
which gives the same result as the Dart code (the use of SpongyCastle is not necessary), but this code should not be used for security reasons.
The following data (the answers to your questions) were taken from https://github.com/leocavalcante/encrypt/blob/5.x/lib/src/encrypted.dart (lines 65-72):
iterationCount = 100
PBKDF2KeyDerivator(Mac('SHA-1/HMAC')) = PBKDF2WithHmacSHA1
The key length is taken from the key instantiation: final key = Key.fromLength(32);
and the salt length is equal to the key length: salt = SecureRandom(desiredKeyLength).bytes;
The algorithm mode defaults in https://github.com/leocavalcante/encrypt/blob/5.x/lib/src/algorithms/aes.dart defaults to SIC
"AES(this.key, {this.mode = AESMode.sic, this.padding = 'PKCS7'}"

External signature on a smart card

I am using a smart card that is signing a SHA-1 hash of a document, and compute a 256 bytes digital signature.
I am using the code posted on this question - iText signing PDF using external signature with smart card.
My problem is that I get the error:" The document has been altered or corrupted since the signature was applied".
I am using a GUI to create the hash and then send the signed 256 bytes that is computed on the card to the signing functions .
Here is my code:
hash creating code of filepath pdf document:
SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
SHA256 sha2 = SHA256.Create();
//sha2.ComputeHash
byte[] pdfBytes = System.IO.File.ReadAllBytes(filePath);
byte[] hash = null;
hash= sha1.ComputeHash(pdfBytes);
the above code is used in one of the GUI functions to create the hash of the document
namespace EIDSmartCardSign
{
class PdfSignature
{
private string outputPdfPath;
private string certPath;
byte[] messageDigest;
private string inputPdfPath;
public PdfSignature(byte[] messageDigest, string inputPdfPath,string outputPdfPath)
{
this.messageDigest = messageDigest;
this.outputPdfPath = outputPdfPath;
this.inputPdfPath = inputPdfPath;
}
public void setCertPath(string certPath)
{
this.certPath = certPath;
}
public void signPdf()
{
X509Certificate2 cert = new X509Certificate2();
cert.Import(certPath); // .cer file certificate obtained from smart card
X509CertificateParser certParse = new Org.BouncyCastle.X509.X509CertificateParser();
Org.BouncyCastle.X509.X509Certificate[] chain = new Org.BouncyCastle.X509.X509Certificate[1] ;
chain[0] = certParse.ReadCertificate(cert.RawData);
X509Certificate2[] certs;
PdfReader reader = new PdfReader(inputPdfPath);
FileStream fout = new FileStream(outputPdfPath,FileMode.Create);
PdfStamper stamper = PdfStamper.CreateSignature(reader, fout, '\0',null,true);
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SignatureCreator = "Me";
appearance.Reason = "Testing iText";
appearance.Location = "On my Laptop";
iTextSharp.text.Rectangle rec = new iTextSharp.text.Rectangle(50, 50, 250, 100);
appearance.SetVisibleSignature(rec, 1, "Signature");
IExternalSignature extSignature= new MyExternalSignature("SHA-1",this.messageDigest);
MakeSignature.SignDetached(appearance, extSignature, chain, null, null, null, 0, CryptoStandard.CMS);
//MakeSignature.
}
}
}
Your hash creating function
SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
SHA256 sha2 = SHA256.Create();
//sha2.ComputeHash
byte[] pdfBytes = System.IO.File.ReadAllBytes(filePath);
byte[] hash = null;
hash = sha1.ComputeHash(pdfBytes);
calculates the wrong hash value.
Have a look at this answer on Information Security Stack Exchange, in particular the sketch
shows that to get the document bytes to sign you do not take the original PDF but instead have to prepare it for integrating the signature container (add signature field, field value with some space reserved for the signature container, and field visualization) and then hash all the bytes except the space reserved for the signature container.
Furthermore, even this naked hash is not the data to sign. Instead a set of attributes is built, one of them containing the document hash calculated as above, other ones containing references to the signer certificate etc., and these attributes are to be signed.
Thus, instead do what you already claimed to be doing:
I am using the code posted on this question - iText signing PDF using external signature with smart card.
In particular the code there does not sign the hash of the whole file but instead uses the data the method Sign of the IExternalSignature implementation receives as parameter which is constructed as explained above.
More details
In a comment the OP said
The card I am working with expects a 20 bytes hash.
20 bytes would be typical for a naked hash generated by either SHA1 or RIPEMD-160. According to your question text, I assume the former algorithm is used. (This by the way indicates that the context does not require a high security level as SHA1 effectively is already phased out or in the process of being phased out for such use cases.)
What steps are needed to further create this hash After hashing the contents of the pdf?
Simply do as in the IExternalSignature implementation in the question you referenced:
public virtual byte[] Sign(byte[] message) {
byte[] hash = null;
using (SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider())
{
hash = sha1.ComputeHash(message);
}
byte[] sig = MySC.GetSignature(hash);
return sig;
}
(Obviously chances are that your smart card signing routine is not called MySC.GetSignature and you have to replace that call accordingly...)
As your card appears to expect a naked hash value in contrast to the card of the OP of the referenced question, this should work for you.
Where can I find examples of creating the aformentioned integrated signature container?
In the examples to the iText white paper Digital Signatures for PDF Documents.
After the signature process, I have 256 bytes signed data, 3 .cer certificates exported from the card.
256 bytes signed data sounds like a naked signature generated using RSA or RSASSA-PSS with a 2048 bit key size.
That been said, you need the signer certificate before signing: In most relevant profiles the signed attributes have to contain a reference to the signer certificate. In the code in the question you referenced that signer certificate is handled here
public void StartTest(){
X509Certificate2 cert = new X509Certificate2();
cert.Import("cert.cer"); // certificate obtained from smart card
X509CertificateParser certParse = new Org.BouncyCastle.X509.X509CertificateParser();
Org.BouncyCastle.X509.X509Certificate[] chain = new Org.BouncyCastle.X509.X509Certificate[] { certParse.ReadCertificate(cert.RawData) };
[...]
MyMakeSignature.SignDetached(appearance, externalSignature, chain, null, null, tsc, 0, CryptoStandard.CADES);
In particular you have to identify the correct signer certificate among those three certificate your card returns; otherwise you might have the same issue as the OP in the referenced question.
How do I create the Contents object when I have all of this data?
Considering what you said about your use case, chances are good that you really merely have to use the code posted of the question iText signing PDF using external signature with smart card with minor adaptions.

Generating Content-MD5 for AWS S3 REST in Haxe

I'm trying to add a Content-MD5 header to my REST calls to AWS S3 in Haxe (compiling to PHP). It's generated by
var contentMD5 = haxe.crypto.Base64.encode(haxe.io.Bytes.ofString(haxe.crypto.Md5.encode(_data)));
with _data in my example being
<Delete><Object><Key>nathan/storage/72ENgrtnpA5VAoy7zEpzPRNEChN0TRGc</Key></Object><Object><Key>nathan/storage/7rlZZSJFvZ7AxUhQZsh4ufn9M2x8m1ae</Key></Object><Object><Key>nathan/storage/HN8NFlUnJiiGo7qlddvRrlGE6hPmWMnZ</Key></Object><Object><Key>nathan/storage/SFsZ8z63DswEVFJQJqmUwbenaWyfZ8zb</Key></Object><Object><Key>nathan/storage/YSYXXgYbSZixOKo27PL65ii6nCeiFesl</Key></Object></Delete>
My full request sent to AWS (for a multiple delete call as described here http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html), using AWS signature version 4 (bucket, signature, and credential shortened):
POST /?delete= HTTP/1.1
Host: mybucket.s3-eu-central-1.amazonaws.com
Content-Length: 392
x-amz-content-sha256: 53da469cb6fc9d0701a1c6ff98d48edd361cd8a90d8a290a2dd224b2681bf7fb
x-amz-date: 20150923T195117Z
Authorization: AWS4-HMAC-SHA256 Credential=zzzzz/20150923/eu-central-1/s3/aws4_request, SignedHeaders=content-md5;host;x-amz-date, Signature=xxxxx
Content-MD5: MDAzNDZmZjJiMGJkMDFkNzVjYzFiOGE4MzI5NTc0NGY=
<Delete><Object><Key>nathan/storage/72ENgrtnpA5VAoy7zEpzPRNEChN0TRGc</Key></Object><Object><Key>nathan/storage/7rlZZSJFvZ7AxUhQZsh4ufn9M2x8m1ae</Key></Object><Object><Key>nathan/storage/HN8NFlUnJiiGo7qlddvRrlGE6hPmWMnZ</Key></Object><Object><Key>nathan/storage/SFsZ8z63DswEVFJQJqmUwbenaWyfZ8zb</Key></Object><Object><Key>nathan/storage/YSYXXgYbSZixOKo27PL65ii6nCeiFesl</Key></Object></Delete>
The response is as follows (RequestId and HostId shortened)
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>InvalidDigest</Code><Message>The Content-MD5 you specified was invalid.</Message><Content-MD5>MDAzNDZmZjJiMGJkMDFkNzVjYzFiOGE4MzI5NTc0NGY=</Content-MD5><RequestId>rrrrrr</RequestId><HostId>ccccccc</HostId></Error>
In my opinion, the generated MD5 is correct. I verified the value with other tools. Also, note that x-amz-content-sha256 is based on the same _data and AWS accepted that header in my previous (non delete) calls.
What am I missing here? Why does my MD5 value differ from the one AWS generates?
You're very close.
Here's the problem:
An md5 hash is 16 bytes in binary representation, 32 characters in hexadecimal representation, and 24 characters (Including padding) in base64.
Yours is approximately twice as long. You appear to be taking the 32 character hex md5, and base64-encoding that, resulting in a base64 string of about 44 characters, instead of just encoding the binary form.
Note that the length of the output, 44 vs 24 is not a factor of two in spite of my assertion that you are encoding 32 initial bytes instead of 16. That's expected, because base64 output_bytes = ceil(input_bytes/3) * 4.
My expertise is with the S3 API -- not haxe, which I've never used -- so the above is almost certainly correct, but the following is wild speculation.
var contentMD5 = haxe.crypto.Base64.encode(haxe.crypto.Md5.make(_data));
Any one out there looking to do this in java can use this code
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.codec.binary.Base64;
public class GenerateMD5 {
public static void main(String args[]) throws Exception{
String s = "<CORSConfiguration> <CORSRule> <AllowedOrigin>http://www.example.com</AllowedOrigin> <AllowedMethod>PUT</AllowedMethod> <AllowedMethod>POST</AllowedMethod> <AllowedMethod>DELETE</AllowedMethod> <AllowedHeader>*</AllowedHeader> <MaxAgeSeconds>3000</MaxAgeSeconds> </CORSRule> <CORSRule> <AllowedOrigin>*</AllowedOrigin> <AllowedMethod>GET</AllowedMethod> <AllowedHeader>*</AllowedHeader> <MaxAgeSeconds>3000</MaxAgeSeconds> </CORSRule> </CORSConfiguration>";
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(s.getBytes());
byte[] digest = md.digest();
StringBuffer sb = new StringBuffer();
/*for (byte b : digest) {
sb.append(String.format("%02x", b & 0xff));
}*/
System.out.println(sb.toString());
StringBuffer sbi = new StringBuffer();
byte [] bytes = Base64.encodeBase64(digest);
String finalString = new String(bytes);
System.out.println(finalString);
}
}
The commented code is where most people get it wrong changing it to hex

How to encode Chinese text in QR barcodes generated with iTextSharp?

I'm trying to draw QR barcodes in a PDF file using iTextSharp. If I'm using English text the barcodes are fine, they are decoded properly, but if I'm using Chinese text, the barcode is decoded as question marks. For example this character '测' (\u6D4B) is decoded as '?'. I tried all supported character sets, but none of them helped.
What combination of parameters should I use for the QR barcode in iTextSharp in order to encode correctly Chinese text?
iText and iTextSharp apparently don't natively support this but you can write some code to handle this on your own. The trick is to get the QR code parser to work with just an arbitrary byte array instead of a string. What's really nice is that the iTextSharp code is almost ready for this but doesn't expose the functionality. Unfortunately many of the required classes are sealed so you can't just subclass them, you'll have to recreate them. You can either download the entire source and add these changes or just create separate classes with the same names. (Please check over the license to make sure you are allowed to do this.) My changes below don't have any error correction so make sure you do that, too.
The first class that you'll need to recreate is iTextSharp.text.pdf.qrcode.BlockPair and the only change you'll need to make is to make the constructor public instead of internal. (You only need to do this if you are creating your own code and not modifying the existing code.)
The second class is iTextSharp.text.pdf.qrcode.Encoder. This is where we'll make the most changes. Add an overload to Append8BitBytes that looks like this:
static void Append8BitBytes(byte[] bytes, BitVector bits) {
for (int i = 0; i < bytes.Length; ++i) {
bits.AppendBits(bytes[i], 8);
}
}
The string version of this method converts text to a byte array and then uses the above so we're just cutting out the middle man. Next, add a new overload to the constructor that takes in a byte array instead of a string. We'll then just cut out the string detection part and force the system to byte-mode, otherwise the code below is pretty much the same.
public static void Encode(byte[] bytes, ErrorCorrectionLevel ecLevel, IDictionary<EncodeHintType, Object> hints, QRCode qrCode) {
String encoding = DEFAULT_BYTE_MODE_ENCODING;
// Step 1: Choose the mode (encoding).
Mode mode = Mode.BYTE;
// Step 2: Append "bytes" into "dataBits" in appropriate encoding.
BitVector dataBits = new BitVector();
Append8BitBytes(bytes, dataBits);
// Step 3: Initialize QR code that can contain "dataBits".
int numInputBytes = dataBits.SizeInBytes();
InitQRCode(numInputBytes, ecLevel, mode, qrCode);
// Step 4: Build another bit vector that contains header and data.
BitVector headerAndDataBits = new BitVector();
// Step 4.5: Append ECI message if applicable
if (mode == Mode.BYTE && !DEFAULT_BYTE_MODE_ENCODING.Equals(encoding)) {
CharacterSetECI eci = CharacterSetECI.GetCharacterSetECIByName(encoding);
if (eci != null) {
AppendECI(eci, headerAndDataBits);
}
}
AppendModeInfo(mode, headerAndDataBits);
int numLetters = dataBits.SizeInBytes();
AppendLengthInfo(numLetters, qrCode.GetVersion(), mode, headerAndDataBits);
headerAndDataBits.AppendBitVector(dataBits);
// Step 5: Terminate the bits properly.
TerminateBits(qrCode.GetNumDataBytes(), headerAndDataBits);
// Step 6: Interleave data bits with error correction code.
BitVector finalBits = new BitVector();
InterleaveWithECBytes(headerAndDataBits, qrCode.GetNumTotalBytes(), qrCode.GetNumDataBytes(),
qrCode.GetNumRSBlocks(), finalBits);
// Step 7: Choose the mask pattern and set to "qrCode".
ByteMatrix matrix = new ByteMatrix(qrCode.GetMatrixWidth(), qrCode.GetMatrixWidth());
qrCode.SetMaskPattern(ChooseMaskPattern(finalBits, qrCode.GetECLevel(), qrCode.GetVersion(),
matrix));
// Step 8. Build the matrix and set it to "qrCode".
MatrixUtil.BuildMatrix(finalBits, qrCode.GetECLevel(), qrCode.GetVersion(),
qrCode.GetMaskPattern(), matrix);
qrCode.SetMatrix(matrix);
// Step 9. Make sure we have a valid QR Code.
if (!qrCode.IsValid()) {
throw new WriterException("Invalid QR code: " + qrCode.ToString());
}
}
The third class is iTextSharp.text.pdf.qrcode.QRCodeWriter and once again we just need to add an overloaded Encode method supports a byte array and that calls are new constructor created above:
public ByteMatrix Encode(byte[] bytes, int width, int height, IDictionary<EncodeHintType, Object> hints) {
ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L;
if (hints != null && hints.ContainsKey(EncodeHintType.ERROR_CORRECTION))
errorCorrectionLevel = (ErrorCorrectionLevel)hints[EncodeHintType.ERROR_CORRECTION];
QRCode code = new QRCode();
Encoder.Encode(bytes, errorCorrectionLevel, hints, code);
return RenderResult(code, width, height);
}
The last class is iTextSharp.text.pdf.BarcodeQRCode which we once again add our new constructor overload:
public BarcodeQRCode(byte[] bytes, int width, int height, IDictionary<EncodeHintType, Object> hints) {
newCode.QRCodeWriter qc = new newCode.QRCodeWriter();
bm = qc.Encode(bytes, width, height, hints);
}
The last trick is to make sure when calling this that you include the byte order mark (BOM) so that decoders know to decode this properly, in this case UTF-8.
//Create an encoder that supports outputting a BOM
System.Text.Encoding enc = new System.Text.UTF8Encoding(true, true);
//Get the BOM
byte[] bom = enc.GetPreamble();
//Get the raw bytes for the string
byte[] bytes = enc.GetBytes("测");
//Combine the byte arrays
byte[] final = new byte[bom.Length + bytes.Length];
System.Buffer.BlockCopy(bom, 0, final, 0, bom.Length);
System.Buffer.BlockCopy(bytes, 0, final, bom.Length, bytes.Length);
//Create are barcode using our new constructor
var q = new BarcodeQRCode(final, 100, 100, null);
//Add it to the document
doc.Add(q.GetImage());
Looks like you may be out of luck. I tried too and got the same results as you did. Then looked at the Java API:
"*CHARACTER_SET the values are strings and can be Cp437, Shift_JIS and
ISO-8859-1 to ISO-8859-16. The default value is ISO-8859-1.*"
Lastly, looked at the iTextSharp BarcodeQRCode class source code to confirm that only those characters sets are supported. I'm by no means an authority on Unicode or encoding, but according to ISO/IEC 8859, the character sets above won't work for Chinese.
Essentially the same trick that Chris has done in his answer could be implemented by specifying UTF-8 charset in barcode hints.
var hints = new Dictionary<EncodeHintType, Object>() {{EncodeHintType.CHARACTER_SET, "UTF-8"}};
var q = new BarcodeQRCode("\u6D4B", 100, 100, hints);
If you want to be more safe, you can start your string with BOM character '\uFEFF', like Chris suggested, so it would be "\uFEFF\u6D4B".
UTF-8 is unfortunately not supported by QR codes specification, and there are a lot of discussions on this subject, but the fact is that most QR code readers will correctly read the code created by this method.