diff --git a/lib/utils/pkcs7-padding.js b/lib/utils/pkcs7-padding.js
--- a/lib/utils/pkcs7-padding.js
+++ b/lib/utils/pkcs7-padding.js
@@ -1,5 +1,19 @@
 // @flow
 
+/**
+ * This file contains functions for PKCS#7 padding and unpadding, as well as
+ * functions for padding on the block basis. This is used to let the padding
+ * to be larger than maximum PKCS#7 padding block size (255 bytes).
+ *
+ * The main idea of this concept is to pad the input using the standard
+ * PKCS#7 padding, and then pad the result to the neares multiple of
+ * superblocks (blocks of blocks). The procedure is analogous
+ * to the standard PKCS#7 padding, but operates on the block basis instead of
+ * the byte basis. The fill value of the padding is the number of blocks added.
+ *
+ * The PKCS#7 padding is described in RFC 5652, section 6.3.
+ */
+
 import invariant from 'invariant';
 
 /**
@@ -39,4 +53,70 @@
   return unpadded;
 }
 
-export { pkcs7pad, pkcs7unpad };
+/**
+ * Pads the PKCS#7-padded input on the block basis. Pads the input to have
+ * a length that is a multiple of the superblock size. The input must already
+ * be PKCS#7 padded to the block size.
+ *
+ * @param {Uint8Array} data - The PKCS#7 padded input to be block-padded.
+ * @param {number} blockSizeBytes - The block size in bytes.
+ * @param {number} superblockSize - The superblock size in blocks.
+ * @returns {Uint8Array} The block-padded data as a new Uint8Array.
+ */
+function superblockPad(
+  data: Uint8Array,
+  blockSizeBytes: number,
+  superblockSize: number,
+): Uint8Array {
+  invariant(
+    data.length % blockSizeBytes === 0,
+    'data length must be a multiple of block size',
+  );
+  invariant(superblockSize > 0, 'superblock size must be positive');
+  invariant(superblockSize <= 255, 'superblock size must be less than 256');
+  invariant(blockSizeBytes > 0, 'block size must be positive');
+  invariant(blockSizeBytes <= 255, 'block size must be less than 256');
+
+  const numBlocks = data.length / blockSizeBytes;
+  const paddingBlocks = superblockSize - (numBlocks % superblockSize);
+  const paddingValue = paddingBlocks;
+
+  const outputBuffer = new Uint8Array(
+    data.length + paddingBlocks * blockSizeBytes,
+  );
+  outputBuffer.set(data);
+  outputBuffer.fill(paddingValue, data.length);
+  return outputBuffer;
+}
+
+/**
+ * Unpads the block-padded input on the block basis.
+ *
+ * @param {Uint8Array} data  - The block-padded input to be unpaded.
+ * @param {number} blockSizeBytes - The block size in bytes.
+ * @returns {Uint8Array} - The unpadded data as a new Uint8Array.
+ */
+function superblockUnpad(data: Uint8Array, blockSizeBytes: number): Uint8Array {
+  invariant(blockSizeBytes > 0, 'block size must be positive');
+  invariant(blockSizeBytes <= 255, 'block size must be less than 256');
+  invariant(
+    data.length % blockSizeBytes === 0,
+    'data length must be a multiple of block size',
+  );
+
+  const numBlocks = data.length / blockSizeBytes;
+  const paddingBlocks = data[data.length - 1];
+  invariant(paddingBlocks > 0 && paddingBlocks < numBlocks, 'invalid padding');
+
+  const unpaddedBlocks = numBlocks - paddingBlocks;
+  const unpaddedLengthBytes = unpaddedBlocks * blockSizeBytes;
+  invariant(
+    data.subarray(unpaddedLengthBytes).every(x => x === paddingBlocks),
+    'invalid padding',
+  );
+
+  const unpaddedData = data.subarray(0, unpaddedLengthBytes);
+  return unpaddedData;
+}
+
+export { pkcs7pad, pkcs7unpad, superblockPad, superblockUnpad };
diff --git a/lib/utils/pkcs7-padding.test.js b/lib/utils/pkcs7-padding.test.js
--- a/lib/utils/pkcs7-padding.test.js
+++ b/lib/utils/pkcs7-padding.test.js
@@ -1,6 +1,11 @@
 // @flow
 
-import { pkcs7pad, pkcs7unpad } from './pkcs7-padding.js';
+import {
+  pkcs7pad,
+  pkcs7unpad,
+  superblockPad,
+  superblockUnpad,
+} from './pkcs7-padding.js';
 
 describe('PKCS#7 Padding', () => {
   it('should pad data to a multiple of blockSize bytes', () => {
@@ -76,6 +81,80 @@
   });
 });
 
+describe('superblock padding', () => {
+  it('should pad data to a multiple of superblockSize blocks', () => {
+    const blockSizeBytes = 16;
+    const superblockSizeBlocks = 4;
+    const dataLengthBytes = 3 * 16;
+    const expectedPaddedLength = 4 * 16;
+    const expectedBlockPadding = 1;
+
+    const data = generateRandomData(dataLengthBytes);
+    const padded = superblockPad(data, blockSizeBytes, superblockSizeBlocks);
+
+    expect(padded.length % expectedPaddedLength).toBe(0);
+    expect(padded[padded.length - 1]).toBe(expectedBlockPadding);
+  });
+
+  it('pad should add a full superblock if input is a multiple of superblockSize blocks', () => {
+    const blockSizeBytes = 16;
+    const superblockSizeBlocks = 4;
+    const dataLengthBytes = 4 * 16;
+    const expectedPaddedLength = 8 * 16;
+    const expectedBlockPadding = 4;
+
+    const data = generateRandomData(dataLengthBytes);
+    const padded = superblockPad(data, blockSizeBytes, superblockSizeBlocks);
+
+    expect(padded.length % expectedPaddedLength).toBe(0);
+    expect(padded[padded.length - 1]).toBe(expectedBlockPadding);
+  });
+
+  it('superblockUnpad should unpad data', () => {
+    const blockSizeBytes = 16;
+
+    // 2 blocks of data + 2 blocks of padding = 4 blocks total (1 superblock)
+    const padded = new Uint8Array([
+      ...generateRandomArray(2 * 16),
+      ...new Array(2 * 16).fill(2),
+    ]);
+
+    const unpadded = superblockUnpad(padded, blockSizeBytes);
+
+    expect(unpadded.length).toBe(32);
+    expect(unpadded).toEqual(padded.subarray(0, 32));
+  });
+
+  it('superblockUnpad should throw if the padding length is 0', () => {
+    const blockSizeBytes = 16;
+    const padded = new Uint8Array([
+      ...generateRandomArray(2 * 16),
+      ...new Array(2 * 16).fill(0),
+    ]);
+    expect(() => superblockUnpad(padded, blockSizeBytes)).toThrow();
+  });
+
+  it('superblockUnpad should throw if the padding length is > num blocks', () => {
+    const blockSizeBytes = 16;
+    // 4 blocks total, but filled with 5s
+    const padded = new Uint8Array([
+      ...generateRandomArray(2 * 16),
+      ...new Array(2 * 16).fill(5),
+    ]);
+    expect(() => superblockUnpad(padded, blockSizeBytes)).toThrow();
+  });
+
+  it('superblockUnpad should throw if the padding is invalid', () => {
+    const blockSizeBytes = 16;
+    const padded = new Uint8Array([
+      ...generateRandomArray(2 * 16),
+      ...generateRandomArray(2 * 15),
+      ...[1],
+    ]);
+    expect(() => superblockUnpad(padded, blockSizeBytes)).toThrow();
+  });
+});
+
 function generateRandomData(length: number): Uint8Array {
   const data = new Uint8Array(length);
   for (let i = 0; i < length; i++) {