Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Generate Digest Example Code

Code Block
languagejava
package com.sunwave;


import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.TimeZone;
import java.util.Date;

import org.apache.commons.codec.digest.DigestUtils;

public class DigestCreator {
    private String userId;
    private String clientId;
    private String clientSecret; // Realm Private Key "select client_secret from sw_external_application where client_id = ? and clinic_id = ?
    private String clinicId;

    private String transactionId; //"select id from sw_api_transaction where transaction_id = ? and clinic_id = ?"

    private String payload;


    private String createMd5Digest() throws NoSuchAlgorithmException, UnsupportedEncodingException {
        return Base64.getEncoder().encodeToString(DigestUtils.md5Hex(payload.getBytes()).getBytes());
    }
    private String createSeed() throws NoSuchAlgorithmException, UnsupportedEncodingException {
        String seed = null;
        if (payload == null) {
            seed = userId + ":" + clientId + ":" + getDateTimeBase64() + ":" + clinicId + ":" + transactionId;
        } else {
            seed = userId + ":" + clientId + ":" + getDateTimeBase64() + ":" + clinicId + ":" + transactionId + ":" + createMd5Digest();
        }
        return seed;
    }

    private String getDateTimeBase64() throws UnsupportedEncodingException {
        SimpleDateFormat df = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z");
        df.setTimeZone(TimeZone.getTimeZone("GMT"));
        Date d = new Date();
        java.sql.Timestamp now = new java.sql.Timestamp(new java.util.Date().getTime());
        String dateTime = df.format(now/*
 * Copyright 2023 Sunwave Health
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.sunwave;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.TimeZone;

import org.apache.commons.codec.digest.DigestUtils;

public class DigestCreator {
    /**
     * userId is the email assigned to the user from sw_user_clinic.user_email.
     * If the combination of userId and clinic_id are not in sw_user_clinic
     * table the validation will fail.
     */
    private final String userId;
    /**
     * clientId is the realm assigned to the user from
     * sw_user_clinic.clinic_id. If the combination of userId and clinic_id
     * are not in sw_user_clinic table the validation will fail.
     */
    private final String clientId;
    /**
     * clinicId is the realm the user has permission to run rest calls against.
     */
    private final String clinicId;
    /**
     * clientSecret is the secret key that was generated by the user's id and
     * clinic id. This comes from the sw_external_application table and is
     * used to calculate the HMAC. The HMAC the DigesterCreator calculates and
     * SunwaveEMR's DigestValidator must match.
     */
    private final String clientSecret;
    /**
     * transactionId is a user chosen string that must be unique for the
     * whole clinic. This value is validated against the sw_api_transaction
     * table by transaction id and clinic id, the row is returned the
     * validation fails.
     */
    private final String transactionId;
    /**
     * payload is only used on POST and PUT HTTP requests and is used to
     * calculate MD5 digest hash.
     */
    private final String payload;

    /**
     * dateTime holds the current time in the following format
     * "Mon, 6 Feb 2023 16:35:45 +0000" as Base64 encoded string. This is
     * generated by getDateTimeBase64. ({@link #getDateTimeBase64()})
     */
    private final String dateTime;

    /**
     * PURPOSE: To calculate the MD5 digest of the payload and return the
     * lower case version of numeric digest as base 64 value.
     * @return Base64 value of the MD5 hash.
     * (@see <a href="https://en.wikipedia.org/wiki/MD5">MD5 Digest</a>)
     */
    private String createMd5Digest() {
        byte[] digest = DigestUtils.md5Hex(payload.getBytes()).getBytes();
        return Base64.getEncoder().encodeToString(digest);
    }

    /**
     * Purpose - To create the seed to be used with the user's private key to
     * generate the HMAC hash. In the case of GET HTTP requests the seed
     * comprises user id, client id, current time as Base64 string
     * {@link #dateTime}, clinic id and transaction id. In the case
     * of HTTP POST and PUT requests the MD5 digest of the payload is added
     * to the seed.
     * @return - Returns seed as clear text.
     */
    private String createSeed() {
        String seed;
        if (payload == null) {
            seed = userId + ":" + clientId + ":" + dateTime + ":"
                + clinicId + ":" + transactionId;
        } else {
            seed = userId + ":" + clientId + ":" + dateTime + ":"
                + clinicId + ":" + transactionId + ":" + createMd5Digest();
        }
        return seed;
    }

    /**
     * PURPOSE: To generate Base64 of the current timestamp. The format is
     * "Mon, 6 Feb 2023 16:42:14 +0000" Note it is using GMT
     * the local +0000 to generate the timestamp.
     * @return - Current time as Base64 string.
     */
    private String getDateTimeBase64() {
        SimpleDateFormat df =
            new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z");
        df.setTimeZone(TimeZone.getTimeZone("GMT"));
        java.sql.Timestamp now =
            new java.sql.Timestamp(new java.util.Date().getTime());
        String currentTime = df.format(now);
        byte[] encodedDate =
            Base64.getEncoder().encode(currentTime.getBytes());
        return new String(encodedDate, StandardCharsets.UTF_8);
    }

    /**
     * Purpose: Create the digest token that can be used in Authorization Head
     * key with value of Digest concatenated with token:
     * "Digest sunwave-admin1:vQl91X514m11dTHHGYQPQ ...". The token consists of
     * user id, client id, current date time, clinic id, transaction id
     * HMAC code in the case of HTTP Get Request. For HTTP PUT and POST the
     * token consists of  user id, client id, current date time, clinic id,
     * transaction id, MD5 digest of the payload and HMAC code.
     * (@see <a href="https://en.wikipedia.org/wiki/Digest_access_authentication">Digest Authentication</a>)
     * @return - digest token
     * @throws NoSuchAlgorithmException - Handles the message signature of
     * {@link #createHMAC(String)}
     * @throws InvalidKeyException - Handles the message signature of
     * {@link #createHMAC(String)}
     */
    private String createToken() throws NoSuchAlgorithmException,
        InvalidKeyException {
        if (payload == null) {
            return userId + ":" + clientId + ":" + dateTime + ":" + clinicId
                + ":" + transactionId + ":" + createHMAC(createSeed());
        } else {
            return userId + ":" + clientId + ":" + dateTime + ":" + clinicId
                + ":" + transactionId + ":" + createMd5Digest() + ":"
                + createHMAC(createSeed());
        }
    }

    /**
     * Purpose: Create Base 64 encoded HMAC hash-based message authentication
     * code (@see <a href="https://en.wikipedia.org/wiki/HMAC">HMAC</a>).
     * Sunwave uses the SHA-512 Secure Hash Algorithm 2
     * (@see <a href="https://en.wikipedia.org/wiki/SHA-2">SHAR-512</a>).
     * @param seed - The seed string to generate the HMAC on.
     * @return - HMAC Code as BAse 64 encoded String.
     * @throws NoSuchAlgorithmException - When Mac class does not support
     * the give hashing algorithm. Note it support HmacSHA512 so no problem.
     * @throws InvalidKeyException - {@link #clientSecret} key can not be
     * used by the SecretKeySpec class.
     */
    private String createHMAC(final String seed)
        throws NoSuchAlgorithmException, InvalidKeyException {
        byte[] byteKey = clientSecret.getBytes(StandardCharsets.UTF_8);
        byte[] encodedDate = Base64.getEncoder().encode(dateTime.getBytes());final String hmacSha512 = "HmacSHA512";
        Mac returnsha512Hmac new= String(encodedDate, "UTF8"Mac.getInstance(hmacSha512);
    }    SecretKeySpec keySpec private= Stringnew createTransactionIdSecretKeySpec(byteKey, hmacSha512);
{         return nullsha512Hmac.init(keySpec);
    }    byte[] macData private= Stringsha512Hmac.
createToken() throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {       doFinal(seed.getBytes(StandardCharsets.UTF_8));
 if (payload == null)    return Base64.getUrlEncoder().encodeToString(macData);
    }


return userId + ":" + clientId + ":" + getDateTimeBase64() + ":" + clinicId + ":" + transactionId + ":" + createHMAC(createSeed());
        else /**
     * Purpose: Create DigestCreator object populated with all the information
     * to generate digest authentication token.
     * @param userId - {@link #userId}
  return userId + ":"* +@param clientId + ":" + getDateTimeBase64() + ":" + clinicId + ":" + transactionId + ":" + createMd5Digest() + ":" + createHMAC(createSeed());
    - {@link #clientId}
     * @param clientSecret - {@link #clientSecret}
     * @param clinicId - {@link #clinicId}
     private String createHMAC(String message) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
        byte[] byteKey = clientSecret.getBytes("UTF-8");* @param transactionId - {@link #transactionId}
     * @param payload - {@link #payload}
     */
    public DigestCreator(final String userId, final String HMAC_SHA512 = "HmacSHA512";clientId,
           Mac sha512_HMAC = Mac.getInstance(HMAC_SHA512);         SecretKeySpec keySpec =final newString SecretKeySpec(byteKeyclientSecret, HMAC_SHA512);final String clinicId,
      sha512_HMAC.init(keySpec);         byte[] mac_data = sha512_HMAC.        final String transactionId, final  doFinal(message.getBytes("UTF-8"));
String payload) {
       return Base64.getUrlEncoder().encodeToString(mac_data);this.userId = userId;
        }this.clientId = clientId;
    public DigestCreator(String userId, String clientId, String clientSecret, String clinic_id, String transactionId, String payload) {this.clientSecret = clientSecret;
        this.clinicId = clinicId;
        this.userIdtransactionId = userIdtransactionId;
        this.clientIdpayload = clientIdpayload;
        this.clientSecretdateTime = clientSecretgetDateTimeBase64();
    }

  this.clinicId = clinic_id;  /**
     * Purpose: Provides command line this.transactionIdinterface =to transactionId;generate digest
     * authentication this.payload = payload;token.
     * @param args - Command line parameters.
    } */
    public static void main(final String args[] ) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyExceptionargs) {
        if ((args.length < 5) || (args.length > 6)) {
            System.out.println("usage: java -cp ./target/DigestCreator-1.0-SNAPSHOT.jar orgcom.baudekinsunwave.DigestCreator [user_id] [clinic_id] [client_id,] [client_secret,] [transaction_id, <pay load>] <payload>");
            System.out.println("java -cp ./target/DigestCreator-1.0-SNAPSHOT.jar com.sunwave.DigestCreator sunwave-admin1 131 vQl91X514m11dTHHGYQPQkxJqNPxgbdJ C0iGincSREijXqeuB3P9sDdj1ZU6UwqVaUc6VLwhpcx2sBQmB85k8zWuIKSc6gkCAcnXm4JTk2YBFpH5fFDEPH0JyKg4SgchallGmNDc9fNkO1ojZxyKaZ5murQZFDvSW7iJl1CM5JESube8P0cdlqtiLoHb7BP4293S6FqG557TbIPS61ACp0lfAOu9fNXD6L2LD24j7QMRZpM8GE6GQOnY5nTaHGn42eBMjB8iMS9gx4P7iStJirC0vjq2miSC 0000002");
            System.exit(-1);
        }
        String payload = null;
        // We are doing post
        if (args.length == 6) {
            payload = args[5];
        }

        DigestCreator dc = new DigestCreator(args[0], args[2], args[3], args[1], args[4], payload);], args[3], args[1], args[4], payload);
        try {
            System.out.println("Token: " + dc.createToken());
        } catch (Exception e) {
            System.outerr.println("Token: " + dc.createToken())Unable to create authentication token.");
            e.printStackTrace(System.err);
        System.out.println("md5 digest: " + dcSystem.createMd5Digestexit(-2));
        }

        System.exit(0);
    }
}

}
Code Block
<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/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>DigestCreater</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <dependencies>
    <dependency>
      <groupId>commons-codec</groupId>
      <artifactId>commons-codec</artifactId>
      <version>1.15</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>3.4.2</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
            <configuration>
              <archive>
                <manifest>
                  <mainClass>
                    com.sunwave.DigestCreator
                  </mainClass>
                </manifest>
              </archive>
              <descriptorRefs>
                <descriptorRef>jar-with-dependencies</descriptorRef>
              </descriptorRefs>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>8</source>
          <target>8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

...