Simplified Guide to Document Signature Creation for LHDN using Java
👋 Hey there! I’m Shohanur Rahman!
I’m a backend developer with over 5.5 years of experience in building scalable and efficient web applications. My work focuses on Java, Spring Boot, and microservices architecture, where I love designing robust API solutions and creating secure middleware for complex integrations.
💼 What I Do Backend Development: Expert in Spring Boot, Spring Cloud, and Spring WebFlux, I create high-performance microservices that drive seamless user experiences. Cloud & DevOps: AWS enthusiast, skilled in using EC2, S3, RDS, and Docker to design scalable and reliable cloud infrastructures. Digital Security: Passionate about securing applications with OAuth2, Keycloak, and digital signatures for data integrity and privacy. 🚀 Current Projects I’m currently working on API integrations with Spring Cloud Gateway and designing an e-invoicing middleware. My projects often involve asynchronous processing, digital signature implementations, and ensuring high standards of security.
📝 Why I Write I enjoy sharing what I’ve learned through blog posts, covering everything from backend design to API security and cloud best practices. Check out my posts if you’re into backend dev, cloud tech, or digital security!
Creating a document signature for LHDN (Lembaga Hasil Dalam Negeri) can be quite challenging. This guide will simplify the steps described in the SDK to help you through the process using Java. We will use a self-signed certificate for demonstration purposes. For actual use, please follow the LHDN e-invoice guide.
Key Concepts
Certificate: A digital document that certifies the ownership of a public key by the named subject.
Private Key: A secret key used for signing data.
Public Key: A public key that can be shared and used to verify signatures.
Keystore: A file that stores keys and certificates.
Digital Signature: A cryptographic value that is calculated from the data and a private key.
Steps to Create a Document Signature
Generate a Self-Signed Certificate
Create a
mykeystore.jksfile that will contain your certificate and private key.Example: Using
keytoolto create a self-signed certificate.keytool -genkeypair -alias myalias -keyalg RSA -keysize 2048 -validity 365 -keystore mykeystore.jks
Load the Certificate and Private Key from Keystore
KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(new FileInputStream("path/to/mykeystore.jks"), "keystore-password".toCharArray()); String alias = "myalias"; X509Certificate certificate = (X509Certificate) keyStore.getCertificate(alias); PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, "key-password".toCharArray());Prepare the JSON Document
- Download a sample invoice payload from here. Your invoice might have different value.
Apply Transformations to JSON
Ensure the JSON document is in UTF-8 format without
UBLExtensionsection.
remove UBLExtensions
Generate Document Digest
- Minify the JSON and compute its SHA-256 hash.
String docString = minifyJson(jsonDocument);
byte[] docBytes = docString.getBytes(StandardCharsets.UTF_8);
byte[] docHash = sha256Hash(docBytes);
String docDigest = encodeBase64(docHash);
Sign the Document Digest
- Use the private key to sign the document digest.
byte[] signHash = signData(docBytes, privateKey);
String sign = encodeBase64(signHash);
/*
function signData()
*/
private byte[] signData(byte[] data, PrivateKey privateKey) throws Exception {
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initSign(privateKey);
signer.update(data);
return signer.sign();
}
Generate Certificate Hash
- Compute the SHA-256 hash of the certificate.
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
byte[] certHash = messageDigest.digest(certificate.getEncoded());
String certDigest = encodeBase64(certHash);
byte[] certificateData = certificate.getEncoded();
String certData = encodeBase64(certificateData);
String certSubject = certificate.getSubjectX500Principal().getName();
String certIssuerName = certificate.getIssuerX500Principal().getName();
String certSerialNumber = certificate.getSerialNumber().toString();
Generate Signing Time
- Create a UTC timestamp for signing.
LocalDateTime currentDateTimeUTC = LocalDateTime.now(ZoneOffset.UTC);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'");
String signingTime = currentDateTimeUTC.format(formatter);
Populate the Signed Properties Section
Update specific nodes in the JSON with the certificate details, signing time, certificate issuerName, certificate issuerSerial.

Generate Signed Properties Hash

- Minify the red marked JSON and compute its SHA-256 hash.
String qualifyingProperties = minifyJson(qualifyingPropertiesNode.toString());
byte[] qualifyingPropertiesHash = sha256Hash(qualifyingProperties.getBytes(StandardCharsets.UTF_8));
byte[] propsDigestHash = sha256Hash(qualifyingPropertiesHash);
String propsDigest = encodeBase64(propsDigestHash);
Populate the Final Document
Update the JSON with the signature, certificate data, and document digests.



Append the UBLExtensions
Add the pupulated
UBLExtensionssection back to the JSON document.
Conclusion
By breaking down the process into these steps, you should be able to create a document signature for LHDN with ease. For a complete implementation, please refer to the provided Java code and adapt it to your specific needs.