1 package org.bouncycastle.crypto.prng.drbg;
2 
3 import org.bouncycastle.crypto.Mac;
4 import org.bouncycastle.crypto.params.KeyParameter;
5 import org.bouncycastle.crypto.prng.EntropySource;
6 import org.bouncycastle.util.Arrays;
7 
8 /**
9  * A SP800-90A HMAC DRBG.
10  */
11 public class HMacSP800DRBG
12     implements SP80090DRBG
13 {
14     private final static long       RESEED_MAX = 1L << (48 - 1);
15     private final static int        MAX_BITS_REQUEST = 1 << (19 - 1);
16 
17     private byte[] _K;
18     private byte[] _V;
19     private long   _reseedCounter;
20     private EntropySource _entropySource;
21     private Mac _hMac;
22     private int _securityStrength;
23 
24     /**
25      * Construct a SP800-90A Hash DRBG.
26      * <p>
27      * Minimum entropy requirement is the security strength requested.
28      * </p>
29      * @param hMac Hash MAC to base the DRBG on.
30      * @param securityStrength security strength required (in bits)
31      * @param entropySource source of entropy to use for seeding/reseeding.
32      * @param personalizationString personalization string to distinguish this DRBG (may be null).
33      * @param nonce nonce to further distinguish this DRBG (may be null).
34      */
HMacSP800DRBG(Mac hMac, int securityStrength, EntropySource entropySource, byte[] personalizationString, byte[] nonce)35     public HMacSP800DRBG(Mac hMac, int securityStrength, EntropySource entropySource, byte[] personalizationString, byte[] nonce)
36     {
37         if (securityStrength > Utils.getMaxSecurityStrength(hMac))
38         {
39             throw new IllegalArgumentException("Requested security strength is not supported by the derivation function");
40         }
41 
42         if (entropySource.entropySize() < securityStrength)
43         {
44             throw new IllegalArgumentException("Not enough entropy for security strength required");
45         }
46 
47         _securityStrength = securityStrength;
48         _entropySource = entropySource;
49         _hMac = hMac;
50 
51         byte[] entropy = getEntropy();
52         byte[] seedMaterial = Arrays.concatenate(entropy, nonce, personalizationString);
53 
54         _K = new byte[hMac.getMacSize()];
55         _V = new byte[_K.length];
56         Arrays.fill(_V, (byte)1);
57 
58         hmac_DRBG_Update(seedMaterial);
59 
60         _reseedCounter = 1;
61     }
62 
hmac_DRBG_Update(byte[] seedMaterial)63     private void hmac_DRBG_Update(byte[] seedMaterial)
64     {
65         hmac_DRBG_Update_Func(seedMaterial, (byte)0x00);
66         if (seedMaterial != null)
67         {
68             hmac_DRBG_Update_Func(seedMaterial, (byte)0x01);
69         }
70     }
71 
hmac_DRBG_Update_Func(byte[] seedMaterial, byte vValue)72     private void hmac_DRBG_Update_Func(byte[] seedMaterial, byte vValue)
73     {
74         _hMac.init(new KeyParameter(_K));
75 
76         _hMac.update(_V, 0, _V.length);
77         _hMac.update(vValue);
78 
79         if (seedMaterial != null)
80         {
81             _hMac.update(seedMaterial, 0, seedMaterial.length);
82         }
83 
84         _hMac.doFinal(_K, 0);
85 
86         _hMac.init(new KeyParameter(_K));
87         _hMac.update(_V, 0, _V.length);
88 
89         _hMac.doFinal(_V, 0);
90     }
91 
92     /**
93      * Return the block size (in bits) of the DRBG.
94      *
95      * @return the number of bits produced on each round of the DRBG.
96      */
getBlockSize()97     public int getBlockSize()
98     {
99         return _V.length * 8;
100     }
101 
102     /**
103      * Populate a passed in array with random data.
104      *
105      * @param output output array for generated bits.
106      * @param additionalInput additional input to be added to the DRBG in this step.
107      * @param predictionResistant true if a reseed should be forced, false otherwise.
108      *
109      * @return number of bits generated, -1 if a reseed required.
110      */
generate(byte[] output, byte[] additionalInput, boolean predictionResistant)111     public int generate(byte[] output, byte[] additionalInput, boolean predictionResistant)
112     {
113         int numberOfBits = output.length * 8;
114 
115         if (numberOfBits > MAX_BITS_REQUEST)
116         {
117             throw new IllegalArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST);
118         }
119 
120         if (_reseedCounter > RESEED_MAX)
121         {
122             return -1;
123         }
124 
125         if (predictionResistant)
126         {
127             reseed(additionalInput);
128             additionalInput = null;
129         }
130 
131         // 2.
132         if (additionalInput != null)
133         {
134             hmac_DRBG_Update(additionalInput);
135         }
136 
137         // 3.
138         byte[] rv = new byte[output.length];
139 
140         int m = output.length / _V.length;
141 
142         _hMac.init(new KeyParameter(_K));
143 
144         for (int i = 0; i < m; i++)
145         {
146             _hMac.update(_V, 0, _V.length);
147             _hMac.doFinal(_V, 0);
148 
149             System.arraycopy(_V, 0, rv, i * _V.length, _V.length);
150         }
151 
152         if (m * _V.length < rv.length)
153         {
154             _hMac.update(_V, 0, _V.length);
155             _hMac.doFinal(_V, 0);
156 
157             System.arraycopy(_V, 0, rv, m * _V.length, rv.length - (m * _V.length));
158         }
159 
160         hmac_DRBG_Update(additionalInput);
161 
162         _reseedCounter++;
163 
164         System.arraycopy(rv, 0, output, 0, output.length);
165 
166         return numberOfBits;
167     }
168 
169     /**
170       * Reseed the DRBG.
171       *
172       * @param additionalInput additional input to be added to the DRBG in this step.
173       */
reseed(byte[] additionalInput)174     public void reseed(byte[] additionalInput)
175     {
176         byte[] entropy = getEntropy();
177         byte[] seedMaterial = Arrays.concatenate(entropy, additionalInput);
178 
179         hmac_DRBG_Update(seedMaterial);
180 
181         _reseedCounter = 1;
182     }
183 
getEntropy()184     private byte[] getEntropy()
185     {
186         byte[] entropy = _entropySource.getEntropy();
187 
188         if (entropy.length < (_securityStrength + 7) / 8)
189         {
190             throw new IllegalStateException("Insufficient entropy provided by entropy source");
191         }
192         return entropy;
193     }
194 }
195