Skip to content

Commit

Permalink
checksum with luhn mod n (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
a-trzewik authored May 20, 2021
1 parent ad42451 commit 6004e3f
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 14 deletions.
69 changes: 58 additions & 11 deletions src/main/java/eu/europa/ec/dgc/issuance/service/DgciGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@

import eu.europa.ec.dgc.issuance.config.IssuanceConfigProperties;
import eu.europa.ec.dgc.issuance.utils.DgciUtil;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import javax.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

Expand All @@ -33,6 +32,25 @@
public class DgciGenerator {
private final IssuanceConfigProperties issuanceConfigProperties;

private static final String CODE_POINTS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/:";

/**
* Check if dgci prefix contains character suitable for checksum calculation.
*/
@PostConstruct
public void checkPrefix() {
String dgciPrefix = issuanceConfigProperties.getDgciPrefix();
if (dgciPrefix != null) {
for (int i = 0;i < dgciPrefix.length();i++) {
if (CODE_POINTS.indexOf(dgciPrefix.charAt(i)) < 0) {
throw new IllegalArgumentException("configured DGCI prefix '"
+ dgciPrefix + "' contains invalid character '"
+ dgciPrefix.charAt(i) + "' only following are supported " + CODE_POINTS);
}
}
}
}

/**
* Generates a new DGCI.
*
Expand All @@ -42,18 +60,47 @@ public String newDgci() {
StringBuilder sb = new StringBuilder();
sb.append(issuanceConfigProperties.getDgciPrefix()).append(':');
sb.append(DgciUtil.encodeDgci(UUID.randomUUID()));
String checkSum = createDgciCheckSum(sb.toString());
sb.append(':').append(checkSum);
sb.append(generateCheckCharacter(sb.toString()));
return sb.toString();
}

private String createDgciCheckSum(String dgciRaw) {
BigInteger dgciRawAsNumber = new BigInteger(1, dgciRaw.getBytes(StandardCharsets.UTF_8));
BigInteger modValue = dgciRawAsNumber.mod(BigInteger.valueOf(97));
String checkSum = modValue.toString();
if (checkSum.length() == 1) {
checkSum = '0' + checkSum;
// see https://en.wikipedia.org/wiki/Luhn_mod_N_algorithm
private char generateCheckCharacter(String input) {
int factor = 2;
int sum = 0;
int n = CODE_POINTS.length();

// Starting from the right and working leftwards is easier since
// the initial "factor" will always be "2".
for (int i = input.length() - 1; i >= 0; i--) {
int codePoint = codePointFromCharacter(input.charAt(i));
int addend = factor * codePoint;

// Alternate the "factor" that each "codePoint" is multiplied by
factor = (factor == 2) ? 1 : 2;

// Sum the digits of the "addend" as expressed in base "n"
addend = (addend / n) + (addend % n);
sum += addend;
}

// Calculate the number that must be added to the "sum"
// to make it divisible by "n".
int remainder = sum % n;
int checkCodePoint = (n - remainder) % n;

return characterFromCodePoint(checkCodePoint);
}

private char characterFromCodePoint(int checkCodePoint) {
return CODE_POINTS.charAt(checkCodePoint);
}

private int codePointFromCharacter(char charAt) {
int codePoint = CODE_POINTS.indexOf(charAt);
if (codePoint < 0) {
throw new IllegalArgumentException("unsupported character for checksum: " + charAt);
}
return checkSum;
return codePoint;
}
}
2 changes: 1 addition & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ springdoc:
swagger-ui:
path: /swagger
issuance:
dgciPrefix: dgci:V1:DE
dgciPrefix: URN:UVCI:V1:DE
keyStoreFile: certs/test.jks
keyStorePassword: dgca
certAlias: dev_ec
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ public class DgciGeneratorTest {
@Test
public void testGenerateDGCI() throws Exception {
IssuanceConfigProperties issuanceConfigProperties = new IssuanceConfigProperties();
issuanceConfigProperties.setDgciPrefix("dgci:V1:DE");
issuanceConfigProperties.setDgciPrefix("URN:UVCI:V1:DE");
DgciGenerator dgciGenerator = new DgciGenerator(issuanceConfigProperties);
String dgci = dgciGenerator.newDgci();
assertNotNull(dgci);
assertTrue(dgci.startsWith(issuanceConfigProperties.getDgciPrefix()));
assertTrue("dgci too long",dgci.length() <= 50);
System.out.println(dgci);
}
}
2 changes: 1 addition & 1 deletion src/test/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ spring:
main:
allow-bean-definition-overriding: true
issuance:
dgciPrefix: dgci:V1:DE
dgciPrefix: URN:UVCI:V1:DE
keyStoreFile: certs/test.jks
keyStorePassword: dgca
certAlias: edgc_dev_ec
Expand Down

0 comments on commit 6004e3f

Please sign in to comment.