HMAC in Spring Boot: Implementation Guide

HMAC (Hash-based Message Authentication Code) is a secure way to protect APIs in Spring Boot. It ensures messages remain unchanged during transmission and verifies the sender’s identity using a shared secret key combined with a hash function (like SHA-256). Unlike basic authentication, HMAC avoids exposing sensitive credentials.
Key Points:
- What is HMAC? A cryptographic method that generates a unique signature for each message using a secret key and a hash function.
- Why use it? It prevents tampering, verifies API requests, and blocks replay attacks when paired with timestamp validation.
- Setup in Spring Boot: Use dependencies like
spring-boot-starter-web,spring-boot-starter-security, andcommons-codec. Ensure stateless sessions, custom headers, and secure key storage. - How it works: Clients sign requests using a secret key. Servers validate by recreating the signature and comparing it with the client’s.
Steps to Implement:
- Generate and store keys securely (e.g. via environment variables or encrypted databases).
- Create HMAC signatures using Java’s
javax.crypto.Macand encode with Base64. - Validate incoming requests by comparing client and server-generated signatures using constant-time comparison.
- Prevent replay attacks by validating timestamps and rejecting outdated requests.
HMAC is widely used by platforms like Stripe and GitHub for securing webhooks and internal APIs. It’s reliable, efficient, and ensures message integrity without over-complicating API security.

HMAC Authentication Implementation Steps in Spring Boot
Setting Up a Spring Boot Project for HMAC
To kick off your HMAC implementation, start by creating a Spring Boot project using Spring Initializr (found at start.spring.io). Select Java 17 or Java 21 as your language version, and make sure to include the Web, Security, and Lombok dependencies. These will serve as the backbone for building a secure API with HMAC-based authentication.
Configuring Spring Boot Dependencies
Here’s a quick rundown of the dependencies you’ll need and their roles:
| Dependency | Purpose |
|---|---|
spring-boot-starter-web | Enables REST API functionality and JSON support via Jackson |
spring-boot-starter-security | Provides the framework for custom security filters |
commons-codec | Simplifies Base64 and Hex encoding/decoding |
lombok | Reduces boilerplate code in models and configurations |
If your implementation involves managing client-specific secret keys, you’ll also want to include spring-boot-starter-data-jpa along with a database driver like PostgreSQL or H2. For those using Maven, it’s a good idea to utilise the spring-security-bom in your <dependencyManagement> section. This ensures consistency in the versions of all security-related modules.
Once you’ve got these dependencies in place, you’re ready to move on to configuring secure session and request handling.
Initial Project Setup
To ensure stateless sessions for your API, configure the security settings to use SessionCreationPolicy.STATELESS. Additionally, disable CSRF protection, as HMAC-secured APIs don’t rely on cookies for authentication.
Since the HttpServletRequest stream can only be read once, you’ll need a way to access the request body multiple times. Implement a CachingHttpRequestWrapper to cache the request body, allowing it to be read repeatedly without issues.
Next, define custom headers such as X-Signature, X-Timestamp, and X-ClientId in your application.yml file. It’s also important to enforce an expiration threshold for requests (e.g., 60,000ms) to guard against replay attacks.
Finally, never hardcode HMAC secrets in your codebase. Instead, store them securely in environment variables or a secure vault. For added protection, encrypt these secrets using an AttributeConverter with AES encryption. This ensures your HMAC secrets remain safe and secure throughout the application lifecycle.
Implementing HMAC Authentication in Spring Boot
To set up HMAC authentication in Spring Boot, you’ll need to handle key generation, build the logic for signature creation, and validate incoming requests using a custom filter.
Generating and Managing Secret Keys
Start by generating secure 32-byte keys using a cryptographic random number generator. You can use the following command:
openssl rand -hex 32 Store these keys securely, either in environment variables or a dedicated secrets manager. If you’re managing keys for individual clients in a database, consider encrypting them with AES before saving. Using a JPA AttributeConverter is a good way to handle this encryption seamlessly. For key rotation, implement a dual-phase approach that supports both old and new keys at the same time. This ensures a smooth transition and allows you to assign unique keys to specific clients or users.
Creating the HMAC Signature Logic
To generate HMAC signatures, use Java’s javax.crypto.Mac with the HmacSHA256 algorithm. Here’s the basic process:
- Initialise a
SecretKeySpecwith the shared secret. - Pass the message bytes into
mac.doFinal(). - Encode the resulting signature in Base64.
For the payload, combine the request body with a UTC timestamp in milliseconds. This ensures each payload is unique, which is a common practice for API authentication and webhook security. When converting strings to bytes, always use StandardCharsets.UTF_8 to avoid platform-specific encoding issues.
Once the signature is generated, you’ll need to verify it against incoming requests.
Validating HMAC Signatures on Incoming Requests
To validate signatures, compare the server-generated signature with the one provided by the client. Use a OncePerRequestFilter to intercept and process requests. The filter should:
- Extract custom headers such as
X-Signature,X-Timestamp, andX-ClientId. - Retrieve the client’s secret key from your secure storage.
- Recreate the HMAC signature on the server side.
For the comparison, use MessageDigest.isEqual() instead of a standard string comparison. This method ensures constant-time comparison, reducing the risk of timing attacks that could expose information about the signature. Additionally, check that the timestamp is within an acceptable range (e.g., 60 seconds) to prevent replay attacks.
To handle the request body multiple times during validation, use a request wrapper that supports repeated reads. Once validated, the request can be passed to your @RestController for further processing.
Preventing Replay Attacks
Once you’ve implemented HMAC authentication, it’s essential to safeguard your API against replay attacks. Without additional measures, a valid signature can be exploited indefinitely unless a timestamp is included. This allows an attacker to capture a legitimate request and replay it at a later time.
To counter this, include a timestamp in your request headers (e.g., X-Timestamp or X-TimestampMillis) and incorporate it into the data used to generate the HMAC. This ensures that any modification to the timestamp invalidates the signature. On the server side, a filter should extract the timestamp, verify the signature to confirm it hasn’t been altered, and check that the request’s timestamp falls within a permissible time window.
Implementing Timestamp Validation
To validate timestamps, calculate the time difference between the server’s current time and the timestamp provided by the client. Using System.currentTimeMillis(), compare the absolute difference between the two. If the difference exceeds the allowed threshold, reject the request.
When generating signatures, combine the request body and timestamp (e.g., requestBody + timestampMillis) to maintain the integrity of the entire request. This prevents attackers from simply updating the timestamp header while leaving the original signature intact.
By implementing these steps, you can ensure that replayed requests are detected and promptly rejected, bolstering your API’s security.
Configuring Expiration Thresholds
To strengthen your API further, define clear expiration thresholds based on your security needs and network conditions. For most production environments, a threshold of 60 seconds (60,000 milliseconds) is common. In high-security scenarios, such as internal microservices, a stricter threshold of 30 seconds can significantly reduce the replay window. For webhooks or mobile clients with higher latency, a 5-minute window might be more suitable.
Store the chosen threshold in a configuration file like application.yml (e.g., hmac.expires=60) to allow for easy adjustments. Additionally, include a small “clock skew” buffer – typically 30 to 60 seconds – to account for minor time synchronisation discrepancies between systems. For extremely sensitive use cases, such as financial transactions, you can add a nonce (a one-time-use random string) alongside the timestamp to eliminate replay risks entirely.
| Threshold Duration | Use Case | Security Level |
|---|---|---|
| 30 Seconds | High-security internal microservices | Very High |
| 60 Seconds | Standard public-facing APIs | High |
| 5 Minutes | Webhooks or high-latency mobile clients | Moderate |
Testing and Validating the HMAC Implementation
Once you’ve implemented HMAC authentication, it’s crucial to thoroughly test it across all possible scenarios. Start with unit tests to confirm the core signature logic. For instance, signing the string “The quick brown fox jumps over the lazy dog” with the key “key” using HmacSHA256 should result in the hex signature:
f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8. This step ensures that the mathematical logic behind your implementation is accurate.
Unit Testing Signature Generation and Validation
To test the HMAC generation method, use assertions like assertEquals to verify consistent results for identical inputs. If your implementation supports multiple algorithms (e.g., HmacSHA256, HmacSHA512), write distinct test cases to ensure Mac.getInstance() handles each algorithm correctly. Pay close attention to encoding formats – some implementations return Base64 strings, while others provide hexadecimal output – so your assertions must align with the expected format. Always convert strings to bytes using StandardCharsets.UTF_8 to avoid discrepancies caused by platform-specific defaults.
Replay protection is another critical aspect to test. Create a scenario with a timestamp older than your configured threshold (e.g., more than 60 seconds) and confirm that the application responds with a 401 Unauthorised status. This test complements earlier measures to prevent replay attacks. Once unit tests confirm the reliability of core functions, move on to integration tests to validate the complete security flow.
End-to-End Testing of API Security
After verifying individual components with unit tests, integration tests simulate the entire API security process. Using Spring’s MockMvc framework, you can replicate the authentication flow without starting a full server. For example, an objectMapper can serialise a request object, append the current system timestamp, and then sign the resulting string with a shared secret. The test should confirm that the API returns a 200 OK status only when the X-Signature header matches the server’s calculated value.
For manual testing with tools like Postman or curl, you’ll need to generate the required headers:
X-ClientId: to retrieve the shared secret.X-TimestampMillis: for replay protection.X-Signature: the HMAC hash.
Ensure these headers are generated correctly. To simplify testing, create a helper method within your tests to dynamically generate these headers. This approach allows you to test various scenarios, such as valid requests, expired timestamps, invalid signatures, or tampered request bodies.
Conclusion
HMAC authentication offers a straightforward and effective way to secure Spring Boot APIs by using a shared secret and SHA-256 hashing. Unlike public-key cryptography, this approach avoids unnecessary complexity while ensuring both the integrity of incoming requests and the authenticity of the sender. This makes it a solid defence against credential sniffing.
This guide has walked through key steps, including configuring filters, managing keys securely, and implementing replay protection with timestamp validation. One critical point to keep in mind is to use constant-time comparison functions, such as MessageDigest.isEqual(), for verifying signatures. Standard string comparisons can expose your application to timing attacks.
As highlighted by industry professionals:
“HMAC provides a robust, efficient way to authenticate messages between trusted parties. By generating strong keys, using constant-time verification, and protecting your secrets… you can secure your webhooks and APIs against tampering and impersonation.” – GitGuardian
To maximise security, deploy 32-byte (256-bit) secret keys stored securely via environment variables or secret management tools. Additionally, enforce a strict expiration window – 300 seconds (5 minutes) or less – to reduce replay attack risks. HMAC continues to be the standard choice for securing stateless RESTful APIs, webhooks (used by services like Stripe, GitHub, and Slack), and internal service-to-service communications.
For tailored advice or further insights on securing your Spring Boot applications with HMAC authentication, consider reaching out to Tucanoo Solutions Ltd.
FAQs
What makes HMAC authentication unique in Spring Boot?
HMAC authentication is unique in its use of a shared secret key to create a hash-based message authentication code from the request data. What makes it particularly secure is that the secret key itself is never sent over the network. Instead, the server and client independently compute hashes using the shared key, and the server verifies the request by comparing its hash with the one provided by the client. This ensures that the data remains intact and hasn’t been altered during transmission.
This method adds a robust layer of protection, guarding sensitive information and blocking unauthorised access. It’s especially suited for situations where maintaining both data integrity and confidentiality is crucial.
How can I securely store HMAC secret keys in a Spring Boot application?
To keep HMAC secret keys safe, never hard-code them into your source code or configuration files. Doing so can expose them to anyone with access to your repository. Instead, rely on a secrets management system like AWS Secrets Manager, Azure Key Vault, or HashiCorp Vault. Another option is to inject the key at runtime using protected environment variables that only your application can access.
If you need to store the key persistently, such as in a database, make sure it is encrypted with a strong master key. Link the encrypted key to a unique identifier for each user or client. This setup makes it easier to rotate keys without disrupting other users. The master key itself should be stored securely and derived using a reliable method like PBKDF2, combined with proper salting.
Stick to the principle of least privilege by limiting access to the secret, monitoring its usage, and rotating keys regularly – ideally every 90 days or immediately following a potential breach. Use random, high-entropy keys that are at least 256 bits long and ensure they remain protected throughout their lifecycle. This reduces the risk of accidental exposure or unauthorised access.
What is the best way to test my HMAC implementation in a Spring Boot application?
To make sure your HMAC implementation in Spring Boot works as expected, you can set up unit tests to check both the signature generation and verification processes. Use a fixed secret key and message during testing, and calculate the expected signature in advance to ensure accuracy.
For instance, create a unit test where your service method generates an HMAC signature, then compare the result to the pre-computed expected value. Similarly, for the verification logic, supply the same payload, signature, and secret key, confirming that the method returns true for valid inputs. Including these tests in your build process can help catch errors early and ensure consistent performance.
By using fixed inputs and outputs, your tests remain predictable, making it easier to identify problems when modifying your code or updating dependencies.
