9.4 EdeficePolicyPinGenerator PIN generation algorithm

The EdeficePolicyPinGenerator PIN generation algorithm is similar to the EdeficePinGenerator PIN generation algorithm, but takes into account the PIN character settings configured on the credential profile. The algorithm uses the card serial number as diversification data. The PIN generation key is used to generate the PIN. If you have the card serial number, the same key that is used within MyID, the details of the PIN character settings in the credential profile, and the details of the following algorithm, you can generate the same PINs as MyID.

Alternatively, you can use the user's logon name as the diversification data; this ensures that the user has the same PIN for all of their cards. To use the logon name, set the Use logon name for server PIN generation option on the PINs page of the Security Settings workflow.

Note: If the credential profile has a PIN policy that allows only numeric characters, the EdeficePolicyPinGenerator PIN generation algorithm generates the same results as the EdeficePinGenerator algorithm.

9.4.1 Generating the PIN

The process for generating the PIN is as follows:

  1. Use the card serial number as the input to a SHA1 hash.

    This generates a 20-byte hash value.

  2. Truncate the 20-byte hash to the first 16 bytes. Encryption is carried out on 8-byte blocks, so we want to carry out the encryption on two blocks without padding.
  3. Encrypt the hash with the PIN generation key.

    • Use 3DES encryption in cipher block chaining mode. This generates a 16-byte hex value.
    • You do not want any header information in the encrypted data.
    • For the initialization vector, use 8 bytes of 0x00.
    • Do not use any padding.
  4. For each byte, divide by the alphabet size (which is determined by the PIN policy in the credential profile) and take the remainder; in other words, <byte> modulo <alphabet size>.

    As there are 16 bytes, you can generate PINs up to 16 characters long. If the PIN is 6 characters long, for example, perform this operation on the first 6 bytes in the encrypted data.

    Note: If you need to insert any mandatory characters (see below), reduce the length of the PIN accordingly; for example, for an 8-character PIN, if both symbols and numbers are mandatory, generate a 6-character PIN. You will insert extra characters for the mandatory character types later to reach the full PIN length.

  5. Use this value as a look-up in the alphabet table – see section 9.4.2, Alphabet tables.

    For example, if the byte is 2C, and the alphabet size is 32 (for a PIN that contains numbers and symbols):

    2C = 44 decimal

    44 modulo 32 = 12

    Entry 12 in the alphabet table = '\'

  6. Insert any mandatory characters.

    If the PIN policy has more than one allowed character type, and the PIN policy has one or more mandatory character types, you must insert one character for each mandatory type. For example, if lower and upper case characters are optional, but numeric and symbol characters are mandatory, insert one number and one symbol.

    If the PIN policy has only one type of character allowed (for example, only numeric) do not insert any mandatory characters; the PIN is already guaranteed to contain characters of that type.

    To generate the mandatory characters, use the same method as for the other characters (take the next byte in the encrypted hash, and use it as an index into the alphabet table), but use the specific alphabet table for the category; for example, for mandatory symbol characters use the symbol table.

    We do not want to append the mandatory characters to the end of the PIN; this is insufficiently random. To determine the position of the inserted character, take a byte from the end of the encrypted hash, and use this modulo the current PIN size plus one to determine the insertion point. For a second mandatory character, use the second-last byte from the encrypted hash, and so on.

    For example, if the last byte is 36, and the current PIN length is 6 characters:

    36 = 54 decimal

    54 modulo 7 = 5

    You can then insert the character at position 5 (this is zero indexed; if the PIN is currently six characters, modulo 7 produces a value between 0 and 6, where 0 means inserting the character at the start, and 6 means inserting the character at the end).

    You must insert the mandatory characters in the following order:

    • Numeric

    • Lower alpha

    • Upper alpha

    • Symbol

9.4.2 Alphabet tables

The alphabet table is generated from the PIN policy, and may comprise the following:

If your PIN policy allows more than one character type, combine the tables in the above order.

9.4.2.1 Numeric

The numeric alphabet has size 10, and the following entries:

Index

0

1

2

3

4

5

6

7

8

9

Value

0 1 2 3 4 5 6 7 8 9

For example, a lookup of 0 returns 0, and a lookup of 7 returns 7.

9.4.2.2 Lower alpha

The lower alpha alphabet has size 26, and has the following entries:

Index

0

1

2

3

4

5

6

7

8

9

Value

a b c d e f g h i j

Index

10

11

12

13

14

15

16

17

18

19

Value

k l m n o p q r s t

Index

20

21

22

23

24

25

       

Value

u v w x y z        

For example, a lookup of 0 returns a, and a lookup of 7 returns h.

9.4.2.3 Upper alpha

The upper alpha alphabet has size 26, and has the following entries:

Index

0

1

2

3

4

5

6

7

8

9

Value

A B C D E F G H I J

Index

10

11

12

13

14

15

16

17

18

19

Value

K L M N O P Q R S T

Index

20

21

22

23

24

25

       

Value

U V W X Y Z        

For example, a lookup of 0 returns a, and a lookup of 7 returns H.

9.4.2.4 Symbol

The symbol alphabet has size 22, and has the following entries:

Index

0

1

2

3

4

5

6

7

8

9

Value

  ! \ " # $ % & ' (

Index

10

11

12

13

14

15

16

17

18

19

Value

) * + - . / : ; = ?

Index

20

21

               

Value

@ ^                

For example, a lookup of 0 returns a space character, and an lookup of 7 returns &.

9.4.2.5 Combined table

For example, for a PIN policy that requires numbers and symbols, the combined table would look like:

Index

0

1

2

3

4

5

6

7

8

9

Value

0 1 2 3 4 5 6 7 8 9

Index

10

11

12

13

14

15

16

17

18

19

Value

  ! \ " # $ % & ' (

Index

20

21

22

23

24

25

26

27

28

29

Value

) * + - . / : ; = ?

Index

30

31

               

Value

@ ^                

You must combine tables in the following order:

9.4.3 Example

If a PIN with a length of eight characters is requested for a card with serial number 1034, and the PIN policy allows numeric, lower alpha, upper alpha, and symbols, with both numeric and symbol characters mandatory, the process is as follows:

  1. The card serial number, 1034, is hashed using SHA1 to produce:

    290448489A06C6A2DC62E82491212444BD6E341F

  2. This is then shortened to 16 bytes, as we want to encode two whole 8-byte blocks:

    290448489A06C6A2DC62E82491212444

  3. This hash is then 3DES CBC mode encrypted using a shared key to produce, for example:

    1F60F51F7D6FF3C316E006C7D239DA36

  4. The PIN required is eight characters, but there are two categories of mandatory characters, so create a 6-digit PIN to begin with. You will insert two mandatory characters later to produce a PIN of the required length.

    All four categories of character are allowed (numeric, lower alpha, upper alpha, and symbols) so the alphabet table is a combination of all four, 84 characters in total.

    The first six bytes from the encrypted hash are used to look up into the alphabet array (size 84). This results in:

    Byte 1 of the encrypted string is 1F (hex) = 31 (dec) mod 84 = 31

    Character 31 in the alphabet table is v

    Byte 2 of the encrypted string is 60 (hex) = 96 (dec) mod 84 = 12

    Character 12 in the alphabet table is c

    Byte 3 of the encrypted string is F5 (hex) = 245 (dec) mod 84 = 77

    Character 77 in the alphabet table is /

    Byte 4 of the encrypted string is 1F (hex) = 31 (dec) mod 84 = 31

    Character 31 in the alphabet table is v

    Byte 5 of the encrypted string is 7D (hex) = 125 (dec) mod 84 = 41

    Character 41 in the alphabet table is F

    Byte 6 of the encrypted string is 6F (hex) = 111 (dec) mod 84 = 27

    Character 27 in the alphabet table is r

    The initial six-character PIN is:

    vc/vFr

  5. Insert the mandatory characters.

    There are two mandatory character types in the eight-digit PIN, so the seventh and eighth bytes of the encrypted hash are used to determine the characters to use, and the last and penultimate bytes of the encrypted hash are used to determine the insertion positions of these characters.

    To add the mandatory numeric character:

    Byte 7 of the encrypted string is F3 (hex) = 243 (dec) mod 10 = 3

    Character 3 in the numeric alphabet table is 3

    For position, take the last byte of the encrypted hash, and find the modulo of 7 (the current PIN size plus 1):

    Byte 16 of the encrypted string is 36 (hex) = 54 (dec) mod 7 = 5

    Inserting 3 into vc/vFr at position 5 (zero indexed) produces a seven-digit PIN of:

    vc/vF3r

    To add the mandatory symbol character:

    Byte 8 of the encrypted string is C3 (hex) = 195 (dec) mod 22 = 19

    Character 19 in the symbol alphabet table is ?

    For position, take the second-last byte of the encrypted hash, and find the modulo of 8 (the current PIN size plus 1):

    Byte 15 of the encrypted string is DA (hex) = 218 (dec) mod 8 = 2

    Inserting ? into vc/vF3r at position 2 (zero indexed) produces a final PIN of:

    vc?/vF3r

    This PIN matches the PIN policy in the credential profile – it is eight characters long, includes both symbols and numbers as mandatory characters, and allows both upper and lower case alpha characters.

9.4.3.1 C# example

The following is sample code that generates PINs using C#. The default settings in the sample code produce the same result as the worked example above.

Copy
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;

namespace PINGeneration
{
  class Program
  {
    static void Main(string[] args)
    {
      char[] numeric = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
      char[] alphaLower = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
        'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
      char[] alphaUpper = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
        'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
      char[] symbols = { ' ', '!', '\\', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', '-', '.', '/', ':', ';', '=', '?', '@', '^' };

      // The policy values are:
      // 0 - Optional
      // 1 - Mandatory
      // 2 - Not allowed
      int numericPinPolicy = 1;
      int lowercasePinPolicy = 0;
      int uppercasePinPolicy = 0;
      int symbolsPinPolicy = 1;

      // Configure the available characters
      StringBuilder allowedSymbolsBuilder = new StringBuilder();
      char[] alphabet = { };
      if (numericPinPolicy != 2)
      {
        alphabet = alphabet.Concat(numeric).ToArray();
        allowedSymbolsBuilder.Append('N');
      }

      if (lowercasePinPolicy != 2)
      {
        alphabet = alphabet.Concat(alphaLower).ToArray();
        allowedSymbolsBuilder.Append('L');
      }

      if (uppercasePinPolicy != 2)
      {
        alphabet = alphabet.Concat(alphaUpper).ToArray();
        allowedSymbolsBuilder.Append('U');
      }

      if (symbolsPinPolicy != 2)
      {
        alphabet = alphabet.Concat(symbols).ToArray();
        allowedSymbolsBuilder.Append('S');
      }

      var allowedSymbols = allowedSymbolsBuilder.ToString();

      // Only need to configure mandatory character set if more than one character type is allowed in the PIN
      StringBuilder mandatorySymbolsBuilder = new StringBuilder();
      if (allowedSymbols.Length > 1)
      {
        if (numericPinPolicy == 1)
          mandatorySymbolsBuilder.Append('N');

        if (lowercasePinPolicy == 1)
          mandatorySymbolsBuilder.Append('L');

        if (uppercasePinPolicy == 1)
          mandatorySymbolsBuilder.Append('U');

        if (symbolsPinPolicy == 1)
          mandatorySymbolsBuilder.Append('S');
      }

      var mandatorySymbols = mandatorySymbolsBuilder.ToString();

      // Encryption key
      byte[] key = { 0x31, 0x28, 0x7A, 0x5A, 0x36, 0x26, 0x35, 0x31, 0x32, 0x71, 0x71, 0x53, 0x3D, 0x2F, 0x33, 0xA7, 0x21, 0x4C, 0x3F, 0x61, 0x44, 0x31, 0x55, 0x38 };

      //Data to be encoded - device serial number
      string data = "1034";

      //Convert to a byte array
      Encoding ascii = Encoding.ASCII;
      byte[] databytes = ascii.GetBytes(data);

      // Create SHA1 hash of data
      SHA1 shaM = new SHA1Managed();
      byte[] hash = SHA1Managed.Create().ComputeHash(Encoding.Default.GetBytes(data));
      byte[] hash16 = new byte[16];

      // Copy the first 16 bytes of the hash array
      Array.Copy(hash, hash16, 16);

      // Set the initialisation vector to 8 bytes of 0x0
      byte[] iv = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };

      string ciphertext = "";
      string pin = "";
      string hashhex = "";
      string hashhex16 = "";

      // Set encryption options.
      TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
      des.KeySize = 192;
      des.Key = key;
      des.Mode = CipherMode.CBC;
      des.Padding = PaddingMode.None;
      des.IV = iv;

      // Encrypt hashed data
      ICryptoTransform ic = des.CreateEncryptor();
      byte[] enc = ic.TransformFinalBlock(hash, 0, 16);
      for (int i = 0; i < enc.Length; i++)
      {
        ciphertext = ciphertext + enc[i].ToString("X2");
      }

      for (int i = 0; i < hash.Length; i++)
      {
        hashhex = hashhex + hash[i].ToString("X2");
      }
      for (int i = 0; i < hash16.Length; i++)
      {
        hashhex16 = hashhex16 + hash16[i].ToString("X2");
      }

      // Generate PIN from the ciphertext
      int pinLength = 8;
      int initialPinLength = pinLength - mandatorySymbols.Length;
      for (int x = 0; x < initialPinLength; x++)
      {
        pin = pin + alphabet[Convert.ToInt32(ciphertext.Substring(x * 2, 2), 16) % alphabet.Length];
      }

      // Now insert the mandatory characters into the pin
      for (int index = 0; index < mandatorySymbols.Length; index++)
      {
        // Do not use the same hash values for selection of both the initial PIN characters and the mandatory characters
        var y = index + initialPinLength;
        char character = ' ';
        switch (mandatorySymbols[index])
        {
          case 'N':
            character = numeric[Convert.ToInt32(ciphertext.Substring(y * 2, 2), 16) % numeric.Length];
            break;

          case 'L':
            character = alphaLower[Convert.ToInt32(ciphertext.Substring(y * 2, 2), 16) % alphaLower.Length];
            break;

          case 'U':
            character = alphaUpper[Convert.ToInt32(ciphertext.Substring(y * 2, 2), 16) % alphaUpper.Length];
            break;

          case 'S':
            character = symbols[Convert.ToInt32(ciphertext.Substring(y * 2, 2), 16) % symbols.Length];
            break;
        }

        // Determine where to insert this character. Use the seed from reverse to determine the character position.
        int position = Convert.ToInt32(ciphertext.Substring(ciphertext.Length - index * 2 - 2, 2), 16) % (pin.Length + 1);
        pin = pin.Insert(position, character.ToString());
      }

      Console.WriteLine("Sample PIN generation algorithm output");
      Console.WriteLine();
      Console.WriteLine("Alphabet size:           " + alphabet.Length);
      Console.WriteLine("Allowed character set:   " + allowedSymbols);
      Console.WriteLine("Mandatory character set: " + mandatorySymbols);
      Console.WriteLine("Required PIN length:     " + pin.Length);
      Console.WriteLine("Data:                    " + data);
      Console.WriteLine("SHA1 hash of data:       " + hashhex);
      Console.WriteLine("16 bytes of hash:        " + hashhex16);
      Console.WriteLine("Encrypted:               " + ciphertext);
      Console.WriteLine("PIN:                     " + pin);
      Console.WriteLine("\nPress any key to continue…");
      Console.ReadKey(true);
    }
  }
}