peass-ng/PEASS-ng · error · InvalidOperationException

outputLength not a multiple of 8

Error message

outputLength not a multiple of 8

What it means

KeccakDigest.Squeeze throws InvalidOperationException when the requested outputLength in bits is not a whole number of bytes ((outputLength & 7) != 0). The squeeze loop produces bytes only, so a bit-level output length is unsupported. Reached via DoFinal with a partial-bit request.

Source

Thrown at winPEAS/winPEASexe/winPEAS/3rdParty/BouncyCastle/crypto/digests/KeccakDigest.cs:256

                    ulong mask = (1UL << partial) - 1UL;
                    state[full] ^= Pack.LE_To_UInt64(dataQueue, off) & mask;
                }
            }

            state[(rate - 1) >> 6] ^= (1UL << 63);

            bitsInQueue = 0;
            squeezing = true;
        }

        protected void Squeeze(byte[] output, int offset, long outputLength)
        {
            if (!squeezing)
            {
                PadAndSwitchToSqueezingPhase();
            }
            if ((outputLength & 7L) != 0L)
                throw new InvalidOperationException("outputLength not a multiple of 8");

            long i = 0;
            while (i < outputLength)
            {
                if (bitsInQueue == 0)
                {
                    KeccakExtract();
                }
                int partialBlock = (int)System.Math.Min((long)bitsInQueue, outputLength - i);
                Array.Copy(dataQueue, (rate - bitsInQueue) >> 3, output, offset + (int)(i >> 3), partialBlock >> 3);
                bitsInQueue -= partialBlock;
                i += partialBlock;
            }
        }

        private void KeccakAbsorb(byte[] data, int off)
        {
            int count = rate >> 6;

View on GitHub (pinned to 53fb989abc)

Solutions

  1. Round the requested output length up to a multiple of 8 bits, then truncate the resulting byte array yourself.
  2. Verify the outputLength parameter passed down from your wrapper is byte-aligned.
  3. For truncated output, generate the next multiple of 8 bits and copy the needed bytes.

Example fix

// before
int outBits = 100; // not a multiple of 8 -> throws
Squeeze(output, outBits);
// after
int outBits = ((100 + 7) / 8) * 8; // 104
byte[] buf = new byte[outBits / 8];
Squeeze(buf, outBits);
byte[] truncated = buf.Take(13).ToArray();
Defensive patterns

Strategy: validation

Validate before calling

if ((outputLengthBits & 7L) != 0L)
    outputLengthBits = (outputLengthBits + 7) / 8 * 8; // round up to byte boundary

Type guard

bool IsByteAligned(long bits) => (bits & 7L) == 0L;

Try / catch

try { Squeeze(buf, outputLengthBits); } catch (InvalidOperationException ex) when (ex.Message.Contains("multiple of 8")) { throw new ArgumentOutOfRangeException(nameof(outputLengthBits), "Must be a multiple of 8"); }

Prevention

When it happens

Trigger: Requesting a digest/DoFinal output whose bit length is not a multiple of 8 (e.g. 254 bits instead of 256) through the partial-bits output path.

Common situations: Custom rate/output-size configuration for Keccak where the developer divides bits by 8 incorrectly; truncated-hash schemes computed in bits.

Related errors


AI-assisted analysis of peass-ng/PEASS-ng@53fb989abc (2026-09-02). Data as JSON: /api/errors/ff8a89b189ecf6b2. Report an issue: GitHub.