Skip to content

Commit 42773ea

Browse files
committed
Argon2: the generator zeroises each block before returning it to the BlockPool, so a custom pool neither has to clear nor can observe password-derived data, and getBlockCount() gives the number of blocks a run takes, relates to github #2452.
1 parent c71f93e commit 42773ea

3 files changed

Lines changed: 258 additions & 47 deletions

File tree

‎core/src/main/java/org/bouncycastle/crypto/generators/Argon2BytesGenerator.java‎

Lines changed: 88 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ public class Argon2BytesGenerator
2525

2626
private static final int ARGON2_SYNC_POINTS = 4;
2727

28+
/* R, Z, addressBlock and inputBlock - the blocks FillBlock takes for the fill step */
29+
private static final int FILL_BLOCK_COUNT = 4;
30+
2831
/* Minimum and maximum number of lanes (degree of parallelism) */
2932
private static final int MIN_PARALLELISM = 1;
3033
private static final int MAX_PARALLELISM = (1 << 24) - 1;
@@ -85,19 +88,45 @@ else if (parameters.getIterations() < MIN_ITERATIONS)
8588
this.parameters = parameters;
8689

8790
// 2. Align memory size
88-
// Minimum memoryBlocks = 8L blocks, where L is the number of lanes
89-
int memoryBlocks = Math.max(parameters.getMemory(), 2 * ARGON2_SYNC_POINTS * parameters.getLanes());
91+
int lanes = parameters.getLanes();
9092

91-
this.segmentLength = memoryBlocks / (ARGON2_SYNC_POINTS * parameters.getLanes());
93+
this.segmentLength = getSegmentLength(parameters.getMemory(), lanes);
9294
this.laneLength = segmentLength * ARGON2_SYNC_POINTS;
93-
94-
// Ensure that all segments have equal length
95-
memoryBlocks = parameters.getLanes() * laneLength;
96-
this.memoryBlocks = memoryBlocks;
95+
this.memoryBlocks = lanes * laneLength;
9796

9897
BlockPool configured = parameters.getBlockPool();
99-
// if no pool is provided hold on to enough blocks for the primary memory
100-
this.pool = (configured != null) ? configured : new FixedBlockPool(memoryBlocks);
98+
// if no pool is provided hold on to every block the generator takes, the fill step's included
99+
this.pool = (configured != null) ? configured : new FixedBlockPool(getBlockCount(parameters.getMemory(), lanes));
100+
}
101+
102+
/**
103+
* Return the number of {@link Block}s a generator takes from its {@link BlockPool} for the
104+
* given memory size and degree of parallelism: the primary memory, after the alignment Argon2
105+
* applies to it, plus the blocks the fill step works in. This is the size to give a
106+
* {@link FixedBlockPool} that is to recycle every block of a run - the alignment rules are an
107+
* implementation detail and are not to be replicated by callers.
108+
*
109+
* @param memory the memory size in 1K blocks, as passed to
110+
* {@link Argon2Parameters.Builder#withMemoryAsKB(int)}.
111+
* @param lanes the degree of parallelism, as passed to
112+
* {@link Argon2Parameters.Builder#withParallelism(int)}.
113+
* @return the number of blocks a generator has outstanding at once.
114+
*/
115+
public static int getBlockCount(int memory, int lanes)
116+
{
117+
if (lanes < MIN_PARALLELISM)
118+
{
119+
throw new IllegalArgumentException("lanes must be at least " + MIN_PARALLELISM);
120+
}
121+
122+
return lanes * getSegmentLength(memory, lanes) * ARGON2_SYNC_POINTS + FILL_BLOCK_COUNT;
123+
}
124+
125+
// Minimum memory is 8L blocks, where L is the number of lanes, and the segments of every lane
126+
// are of equal length, so the memory actually used is a multiple of ARGON2_SYNC_POINTS * lanes.
127+
private static int getSegmentLength(int memory, int lanes)
128+
{
129+
return Math.max(memory, 2 * ARGON2_SYNC_POINTS * lanes) / (ARGON2_SYNC_POINTS * lanes);
101130
}
102131

103132
public int generateBytes(char[] password, byte[] out)
@@ -124,12 +153,19 @@ public int generateBytes(byte[] password, byte[] out, int outOff, int outLen)
124153

125154
byte[] tmpBlockBytes = new byte[ARGON2_BLOCK_SIZE];
126155

127-
allocateMemory();
128-
initialize(tmpBlockBytes, password, outLen);
129-
fillMemoryBlocks();
130-
digest(tmpBlockBytes, out, outOff, outLen);
131-
132-
reset();
156+
try
157+
{
158+
allocateMemory();
159+
initialize(tmpBlockBytes, password, outLen);
160+
fillMemoryBlocks();
161+
digest(tmpBlockBytes, out, outOff, outLen);
162+
}
163+
finally
164+
{
165+
// whatever happened, the password-derived material goes back zeroised
166+
Arrays.clear(tmpBlockBytes);
167+
reset();
168+
}
133169

134170
return outLen;
135171
}
@@ -145,7 +181,7 @@ private void allocateMemory()
145181
}
146182
}
147183

148-
// Return primary memory to the BlockPool.
184+
// Return primary memory to the BlockPool, zeroised - see BlockPool.
149185
private void reset()
150186
{
151187
if (null != memory)
@@ -155,7 +191,7 @@ private void reset()
155191
Block b = memory[i];
156192
if (null != b)
157193
{
158-
pool.deallocate(b);
194+
pool.deallocate(b.clear());
159195
}
160196
}
161197
}
@@ -165,24 +201,30 @@ private void reset()
165201
private void fillMemoryBlocks()
166202
{
167203
FillBlock filler = new FillBlock(pool);
168-
Position position = new Position();
169-
for (int pass = 0; pass < parameters.getIterations(); ++pass)
204+
try
170205
{
171-
position.pass = pass;
172-
173-
for (int slice = 0; slice < ARGON2_SYNC_POINTS; ++slice)
206+
Position position = new Position();
207+
for (int pass = 0; pass < parameters.getIterations(); ++pass)
174208
{
175-
position.slice = slice;
209+
position.pass = pass;
176210

177-
for (int lane = 0; lane < parameters.getLanes(); ++lane)
211+
for (int slice = 0; slice < ARGON2_SYNC_POINTS; ++slice)
178212
{
179-
position.lane = lane;
213+
position.slice = slice;
180214

181-
fillSegment(filler, position);
215+
for (int lane = 0; lane < parameters.getLanes(); ++lane)
216+
{
217+
position.lane = lane;
218+
219+
fillSegment(filler, position);
220+
}
182221
}
183222
}
184223
}
185-
filler.deallocate(pool);
224+
finally
225+
{
226+
filler.deallocate(pool);
227+
}
186228
}
187229

188230
private void fillSegment(FillBlock filler, Position position)
@@ -570,10 +612,10 @@ private static class FillBlock
570612

571613
void deallocate(BlockPool pool)
572614
{
573-
pool.deallocate(addressBlock);
574-
pool.deallocate(inputBlock);
575-
pool.deallocate(R);
576-
pool.deallocate(Z);
615+
pool.deallocate(addressBlock.clear());
616+
pool.deallocate(inputBlock.clear());
617+
pool.deallocate(R.clear());
618+
pool.deallocate(Z.clear());
577619
}
578620

579621
private void applyBlake()
@@ -731,6 +773,15 @@ private static class Position
731773
* {@code generateBytes} calls. Implementations must accept matching
732774
* allocate/deallocate pairs - the generator does not guard against
733775
* double-deallocation of the same block.
776+
* <p>
777+
* The generator zeroises a block before passing it to
778+
* {@link #deallocate(Block)}, so an implementation never receives
779+
* password-derived data and need not clear anything itself; a block it
780+
* hands out from {@link #allocate()} may be returned as it came back.
781+
* {@link Block#clear()} is public for an implementation that wants to
782+
* zeroise blocks it obtained some other way. Size a pool that is to
783+
* recycle a whole run with
784+
* {@link Argon2BytesGenerator#getBlockCount(int, int)}.
734785
*/
735786
public static interface BlockPool
736787
{
@@ -742,9 +793,10 @@ public static interface BlockPool
742793
/**
743794
* Bounded pool that recycles up to {@code maxBlocks} {@link Block} objects.
744795
* Excess blocks returned via {@link #deallocate(Block)} are dropped and
745-
* left for the garbage collector. Returned blocks are zeroised both on
746-
* deallocation and again on allocation, so a recycled block is never
747-
* observed with stale data.
796+
* left for the garbage collector. The generator returns every block
797+
* zeroised, so this pool neither clears nor inspects what it recycles;
798+
* {@link Argon2BytesGenerator#getBlockCount(int, int)} gives the size that
799+
* recycles a whole run.
748800
*/
749801
public static class FixedBlockPool
750802
implements BlockPool
@@ -762,27 +814,18 @@ public FixedBlockPool(int maxBlocks)
762814

763815
public Block allocate()
764816
{
765-
Block block = null;
766817
synchronized (blocks)
767818
{
768819
if (!blocks.isEmpty())
769820
{
770-
block = (Block)blocks.remove(blocks.size() - 1);
821+
return (Block)blocks.remove(blocks.size() - 1);
771822
}
772823
}
773-
if (block == null)
774-
{
775-
return new Block();
776-
}
777-
// a deallocate() in another thread may not have published its clear()
778-
// - re-clear here so callers see a zeroised block.
779-
block.clear();
780-
return block;
824+
return new Block();
781825
}
782826

783827
public void deallocate(Block block)
784828
{
785-
block.clear();
786829
synchronized (blocks)
787830
{
788831
if (blocks.size() < maxBlocks)

0 commit comments

Comments
 (0)