Skip to main content

Command Palette

Search for a command to run...

Simplified Guide to Document Signature Creation for LHDN using Java

Updated
3 min read
S

👋 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

  1. Generate a Self-Signed Certificate

    • Create a mykeystore.jks file that will contain your certificate and private key.

    • Example: Using keytool to create a self-signed certificate.

        keytool -genkeypair -alias myalias -keyalg RSA -keysize 2048 -validity 365 -keystore mykeystore.jks
      
  2. 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());
    
  3. Prepare the JSON Document

    • Download a sample invoice payload from here. Your invoice might have different value.
  4. Apply Transformations to JSON

    • Ensure the JSON document is in UTF-8 format without UBLExtension section.

      remove UBLExtensions

  5. 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);
  1. 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();
        }
  1. 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();
  1. 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);
  1. Populate the Signed Properties Section

    • Update specific nodes in the JSON with the certificate details, signing time, certificate issuerName, certificate issuerSerial.

  2. 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);
  1. Populate the Final Document

    • Update the JSON with the signature, certificate data, and document digests.

  2. Append the UBLExtensions

    • Add the pupulated UBLExtensions section 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.

More from this blog

Shohanur Rahman's blog

69 posts