@@ -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