Showing posts with label security. Show all posts
Showing posts with label security. Show all posts

Friday, 3 November 2023

Java Utility Class for Digital Signature with RSA and DSA Support

In this post, I am going to develop a Java utility class that provides the following functionalities.

a.   Generate key pairs for RSA, DSA algorithms.

b.   Generate a digital signature for a string message using both RSA and DSA.

c.    Validate the digital signature for a string message using the corresponding public key.

d.   Support parameterization of the algorithm (e.g., "SHA256withRSA", "SHA256withDSA"), key size, and key pair generation algorithms (RSA and DSA).

 

Let’s define enums to represent key pair algorithms and their key sizes.


KeyPairAlgorithm.java

package com.sample.app.security;

public enum KeyPairAlgorithm {
    RSA_1024("RSA", 1024), RSA_2048("RSA", 2048), RSA_3072("RSA", 3072), RSA_4096("RSA", 4096), DSA_1024("DSA", 1024),
    DSA_2048("DSA", 2048), DSA_3072("DSA", 3072), ECDSA_256("ECDSA", 256), ECDSA_384("ECDSA", 384),
    ECDSA_521("ECDSA", 521);

    private final String algorithm;
    private final Integer keyLength;

    private KeyPairAlgorithm(String algorithm, int keyLength) {
        this.algorithm = algorithm;
        this.keyLength = keyLength;
    }

    public String getAlgorithm() {
        return algorithm;
    }

    public Integer getKeyLength() {
        return keyLength;
    }

}

 

Let’s define SignatureAlgorithm that define different algorithms that support digital signature generation.

 

SignatureAlgorithm.java

 

package com.sample.app.security;

public enum SignatureAlgorithm {
    SHA1withRSA("SHA1withRSA"), SHA256withRSA("SHA256withRSA"), SHA384withRSA("SHA384withRSA"),
    SHA512withRSA("SHA512withRSA"), SHA1withDSA("SHA1withDSA"), SHA256withDSA("SHA256withDSA"),
    SHA384withDSA("SHA384withDSA"), SHA512withDSA("SHA512withDSA"), SHA1withECDSA("SHA1withECDSA"),
    SHA256withECDSA("SHA256withECDSA"), SHA384withECDSA("SHA384withECDSA"), SHA512withECDSA("SHA512withECDSA");

    private final String algorithm;

    private SignatureAlgorithm(String algorithm) {
        this.algorithm = algorithm;
    }

    public String getAlgorithm() {
        return algorithm;
    }
}

 

Let’s define an utility class to generate keypair, digital signature, validate digital signature etc.,

 

SignatureUtil.java

package com.sample.app.security;

import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class SignatureUtil {

    public static KeyPair generateKeyPair(String algorithm, int keySize) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
        keyPairGenerator.initialize(keySize);
        return keyPairGenerator.generateKeyPair();
    }

    public static String privateKeyToString(PrivateKey privateKey) {
        byte[] privateKeyBytes = privateKey.getEncoded();
        return Base64.getEncoder().encodeToString(privateKeyBytes);
    }

    public static String publicKeyToString(PublicKey publicKey) {
        byte[] publicKeyBytes = publicKey.getEncoded();
        return Base64.getEncoder().encodeToString(publicKeyBytes);
    }

    public static PrivateKey privateKeyFromString(String privateKeyStr, String algorithm) throws Exception {
        byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyStr);
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
        return keyFactory.generatePrivate(keySpec);
    }

    public static PublicKey publicKeyFromString(String publicKeyStr, String algorithm) throws Exception {
        byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyStr);
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
        return keyFactory.generatePublic(keySpec);
    }

    public static String signMessage(String message, PrivateKey privateKey, String algorithm) throws Exception {
        Signature signature = Signature.getInstance(algorithm);
        signature.initSign(privateKey);
        signature.update(message.getBytes("UTF-8"));
        byte[] signatureBytes = signature.sign();
        return Base64.getEncoder().encodeToString(signatureBytes);
    }

    public static boolean verifySignature(String message, String signatureStr, PublicKey publicKey, String algorithm) throws Exception {
        Signature signature = Signature.getInstance(algorithm);
        signature.initVerify(publicKey);
        signature.update(message.getBytes("UTF-8"));
        byte[] signatureBytes = Base64.getDecoder().decode(signatureStr);
        return signature.verify(signatureBytes);
    }

   
}

Let’s define the demo class to validate the same.

 

SignatureUtilDemo.java

package com.sample.app;

import java.security.KeyPair;

import com.sample.app.security.KeyPairAlgorithm;
import com.sample.app.security.SignatureAlgorithm;
import com.sample.app.security.SignatureUtil;

public class SignatureUtilDemo {
    public static void main(String[] args) throws Exception {
        KeyPairAlgorithm keyPairAlgorithm = KeyPairAlgorithm.RSA_4096;
        String algorithmToGenerateKeyPair = keyPairAlgorithm.getAlgorithm();
        int keySize = keyPairAlgorithm.getKeyLength();

        String signatureAlgorithm = SignatureAlgorithm.SHA512withRSA.getAlgorithm();

        KeyPair keyPair = SignatureUtil.generateKeyPair(algorithmToGenerateKeyPair, keySize);

        String privateKeyStr = SignatureUtil.privateKeyToString(keyPair.getPrivate());
        String publicKeyStr = SignatureUtil.publicKeyToString(keyPair.getPublic());

        System.out.println("Private Key: " + privateKeyStr);
        System.out.println("\nPublic Key: " + publicKeyStr);

        String message = "Hello, world!";
        String signature = SignatureUtil.signMessage(message, keyPair.getPrivate(), signatureAlgorithm);

        System.out.println("\nMessage: " + message);
        System.out.println("\nSignature: " + signature);

        boolean signatureValid = SignatureUtil.verifySignature(message, signature, keyPair.getPublic(),
                signatureAlgorithm);

        System.out.println("\nSignature Valid: " + signatureValid);
    }
}



 

Previous                                                 Next                                                 Home

Monday, 23 March 2020

Swagger: Specify security like basic, oauth2

'securitySchemes' element is used to define the security scheme that is used by the APIs.
components:
  securitySchemes:
    myOauth:
      type: oauth2
      flows:
        password:
          tokenUrl: 'http://sample.com/token'
          scopes:
            write: Used to modify resources
            read: Used to read resources
            
    myBasic:
      type: http
      scheme: basic

You can use the defined security scheme in the APIs like below.
  /employees/{employeeId}:
    get:
      security:
        - myBasic: []

/employees:
      post:
        security: 
          - myOauth: [write]

data.yaml
openapi: 3.0.0
info:
  title: Customer Data Aceess API
  description: API to expose all the CRUD operations on  customers
  contact:
    name: Krishna
    email: krishna123@abc.com
    url: https://self-learning-java-tutorial.blogspot.com/
  version: 1.0.0
paths:

  /employees/{employeeId}:
    get:
      security:
        - myBasic: []
      parameters: 
      - in: path
        name: employeeId
        required: true
        schema:
          type: integer
          example: 123
      responses:
        200:
          description: Get Specific Employee Details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/employee'
            application/xml:
              schema:
                $ref: '#/components/schemas/employee'
            application/csv:
              schema:
                $ref: '#/components/schemas/employee'
        403:
          $ref: '#/components/responses/403Unauthorized'
        500:
          $ref: '#/components/responses/500InternalServerError'
          
  /employees:
      post:
        security: 
          - myOauth: [write]
        description: Add new employee to the organization
        requestBody:
          content:
            application/json:
              schema:
                type: object
                properties:
                  firstName:
                    type: string
                    example: krishna
                  lastName:
                    type: string
                    example: gurram
        responses:
          201:
            description: New Employee is created
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/employee'
          403:
            $ref: '#/components/responses/403Unauthorized'
          500:
            $ref: '#/components/responses/500InternalServerError'
                  
      get:
        parameters: 
          - $ref: '#/components/parameters/pageSize'
          - $ref: '#/components/parameters/pageNumber'
          - in: header
            name: onetime_token
            description: token to be used at the time of login
            required: false
            schema:
              type: string
              example: 08c3372a-9314-49d6-b9dd-ff212c1715a5
        responses:
          200:
            description: List of all the employees in organization
            content:
               application/json:
                  schema:
                    type: array
                    items:
                      $ref: '#/components/schemas/employee'
          403:
            $ref: '#/components/responses/403Unauthorized'
          500:
            $ref: '#/components/responses/500InternalServerError'

components:
  securitySchemes:
    myOauth:
      type: oauth2
      flows:
        password:
          tokenUrl: 'http://sample.com/token'
          scopes:
            write: Used to modify resources
            read: Used to read resources
            
    myBasic:
      type: http
      scheme: basic
      
  parameters:
    pageSize:  
      in: query
      name: size
      description: Number of elements to return
      required: false
      schema:
        type: integer
        example: 10
        minimum: 10
        maximum: 100
        
    pageNumber:
      in: query
      name: from
      description: Page number to return
      required: true
      schema:
        type: integer
        example: 1
        
  responses:
    403Unauthorized:
      description: User do not have permission to access this API
      content:
        application/json:
          schema:
            type: object
            properties:
              statusCode:
                type: integer
                example: 403
              message:
                type: string
                example: Unauthorized
    500InternalServerError:
      description: Unable to process the request
      content:
        application/json:
          schema:
            type: object
            properties:
              statusCode:
                type: integer
                example: 500
              message:
                type: string
                example: Internal Server Error
    404NotFoundError:
      description: Service Not Found
      content:
        application/json:
          schema:
            type: object
            properties:
              statusCode:
                type: integer
                example: 404
              message:
                type: string
                example: Service not found
  schemas:
    employee:
      type: object
      required: 
        - firstName
        - id
      properties:
        id:
          type: integer
          example: 1234
        firstName:
          type: string
          example: krishna
        lastName:
          type: string
          example: gurram

Open the content of data.yaml in swagger editor, you can see the security is enabled (lock icon) for below APIs.

GET /employees/{employeeId}
POST /employees



Previous                                                    Next                                                    Home

Monday, 12 August 2019

Spring Security: Exclude public apis from authentication

In my previous post, I explained how to safe guard complete application using spring-secure project.

In any typical web application, there are public resources like home page, style sheets, java script files, images etc., we no need to safe guard these resources using spring-security.

How to exclude some resources from safe guarding?
By extending WebSecurityConfigurerAdapter class and overriding configure method, we can tell to spring security, which urls should be excluded and which should be safe guarded.
@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf().disable().authorizeRequests().antMatchers("/", "/public/*", "/css/*", "/js/*").permitAll()
                .anyRequest().authenticated().and().httpBasic();

    }
}


Above snippet disable csrf security and exclude the urls /, "/public/*", "/css/*", "/js/*" from safe guarding and all other urls are safe guarded by basic authentication.

Find the below working application.

HelloWorldController.java    
package com.sample.app.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloWorldController {
 @RequestMapping("/")
 public String homePage() {
  return "Welcome to Spring boot Application Development using Spring Security";
 }
 
 @RequestMapping("/public/aboutme")
 public String aboutMe() {
  return "I am securied by spring security module";
 }
 
}


EmployeeController.java
package com.sample.app.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("employees/")
public class EmployeeController {

 @RequestMapping(value = "registered/count", method = RequestMethod.GET)
 public String countEmps() {
  return "Total Registered Employees : "+  1024;
 }
}


ApplicationSecurityConfiguration.java
package com.sample.app.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfiguration extends WebSecurityConfigurerAdapter {

 @Override
 protected void configure(HttpSecurity httpSecurity) throws Exception {
  httpSecurity.csrf().disable().authorizeRequests().antMatchers("/", "/public/*", "/css/*", "/js/*").permitAll()
    .anyRequest().authenticated().and().httpBasic();

 }
}


App.java

package com.sample.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {
 public static void main(String[] args) {
  
  SpringApplication.run(App.class, args);
 }
}


pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>springSecurity</groupId>
 <artifactId>springSecurity</artifactId>
 <version>1</version>

 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.1.6.RELEASE</version>
 </parent>

 <dependencies>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
  </dependency>

 </dependencies>
</project>


Total project structure looks like below.

Run App.java, you can see below kind of messages in console.
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.6.RELEASE)

2019-07-14 13:15:17.974  INFO 19933 --- [           main] com.sample.app.App                       : Starting App on C02X902SJGH5 with PID 19933 (/Users/krishna/Documents/EclipseWorkSpaces/Learnings/springSecurity/target/classes started by krishna in /Users/krishna/Documents/EclipseWorkSpaces/Learnings/springSecurity)
2019-07-14 13:15:17.976  INFO 19933 --- [           main] com.sample.app.App                       : No active profile set, falling back to default profiles: default
2019-07-14 13:15:18.942  INFO 19933 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-07-14 13:15:18.971  INFO 19933 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-07-14 13:15:18.972  INFO 19933 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.21]
2019-07-14 13:15:19.079  INFO 19933 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-07-14 13:15:19.080  INFO 19933 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1052 ms
2019-07-14 13:15:19.294  INFO 19933 --- [           main] .s.s.UserDetailsServiceAutoConfiguration : 

Using generated security password: f4be2aa8-c4b3-4ab3-bef8-96ce4e196c81

2019-07-14 13:15:19.401  INFO 19933 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@4925f4f5, org.springframework.security.web.context.SecurityContextPersistenceFilter@40147317, org.springframework.security.web.header.HeaderWriterFilter@2577d6c8, org.springframework.security.web.authentication.logout.LogoutFilter@e044b4a, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@7f4037ed, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@19542407, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@76304b46, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@1ad926d3, org.springframework.security.web.session.SessionManagementFilter@4c9e9fb8, org.springframework.security.web.access.ExceptionTranslationFilter@30cdae70, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@4aeaadc1]
2019-07-14 13:15:19.537  INFO 19933 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-07-14 13:15:19.803  INFO 19933 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-07-14 13:15:19.806  INFO 19933 --- [           main] com.sample.app.App                       : Started App in 2.122 seconds (JVM running for 2.484)

Search for string ‘password’ in console messages, you can see below kind of message.

Using generated security password: f4be2aa8-c4b3-4ab3-bef8-96ce4e196c81

We require above password to access the apis that are protected by spring security module. This password change for every run of the application.

Open the url ‘http://localhost:8080/’ in browser, you can see below kind of screen.

Open the url ‘http://localhost:8080/public/aboutme’, you can see below kind of screen.

Open the url ‘http://localhost:8080/employees/registered/count’, then browser prompts for the credentials to access the api.

Enter the user name as ‘user’ and password as ‘f4be2aa8-c4b3-4ab3-bef8-96ce4e196c81’ (password should be taken from console messages).


After click on OK button, you can see below kind of screen.


You can download complete working application from this link.

Previous                                                    Next                                                    Home