PRESENT Encryption
At a Glance
- 64-bit block size
- 80-bit or 128-bit key
- 31 rounds
- SPN structure
- Designed for IoT & RFID
About PRESENT
Designed by Bogdanov et al. in 2007
PRESENT is an ultra-lightweight block cipher designed specifically for constrained environments such as RFID tags and sensor networks where resources like processing power, memory, and energy are extremely limited. It was designed by Andrey Bogdanov, Lars R. Knudsen, Gregor Leander, Christof Paar, Axel Poschmann, Matthew J. B. Robshaw, Yannick Seurin, and C. Vikkelsoe.
The cipher was presented at CHES 2007 and has been standardized in the ISO/IEC 29192-2:2019 lightweight cryptography standard. PRESENT is notable for its extremely small hardware footprint and efficiency, making it ideal for applications where AES would be too resource-intensive.
Key Features
- Extremely small hardware footprint (1570 GE for 80-bit key version)
- Optimized for hardware implementation rather than software
- Simple and elegant design with minimal operations
- Bit-oriented permutation layer for optimal diffusion
- 4-bit S-box with good cryptographic properties
- Suitable for passive RFID tags, sensor nodes, and other constrained devices
- Competitive security margin despite lightweight design
Technical Specifications
- Block size: 64 bits
- Key size: 80 bits (standard) or 128 bits
- Number of rounds: 31
- Structure: Substitution-Permutation Network (SPN)
- S-box size: 4 bits (16 possible values)
- Permutation layer: Bit-by-bit permutation
- Key schedule: Simple with round counter
- Operations: Only XOR, S-box lookup, and bit permutation
How PRESENT Works
PRESENT follows the classic Substitution-Permutation Network (SPN) structure and consists of 31 rounds. Each round involves three simple operations:
1. AddRoundKey
The 64-bit state is XORed with the round key derived from the key schedule. This is a simple bitwise XOR operation.
2. S-box Layer
The state is divided into sixteen 4-bit nibbles, and each nibble is processed through a 4-bit S-box (substitution box). The S-box provides confusion.
3. Permutation Layer
Each bit of the state is moved to a new position according to a fixed permutation table. This provides diffusion across the entire state.
S-box (4-bit Substitution)
S-box = [ 0xC, 0x5, 0x6, 0xB, 0x9, 0x0, 0xA, 0xD, 0x3, 0xE, 0xF, 0x8, 0x4, 0x7, 0x1, 0x2 ]
Permutation Layer (Bit Position Mapping)
P-Layer: bit i is moved to position P(i) P = [ 0, 16, 32, 48, 1, 17, 33, 49, 2, 18, 34, 50, 3, 19, 35, 51, 4, 20, 36, 52, 5, 21, 37, 53, 6, 22, 38, 54, 7, 23, 39, 55, 8, 24, 40, 56, 9, 25, 41, 57, 10, 26, 42, 58, 11, 27, 43, 59, 12, 28, 44, 60, 13, 29, 45, 61, 14, 30, 46, 62, 15, 31, 47, 63 ]
Key Schedule (80-bit version)
The key schedule for PRESENT is deliberately simple to maintain the lightweight nature of the cipher:
- The 80-bit key is stored in a key register K = k79k78...k0
- The round key consists of the 64 leftmost bits of the current key register: Ki = k79k78...k16
- After extracting the round key, the key register is updated as follows:
- The key register is rotated to the left by 61 bit positions
- The leftmost 4 bits are passed through the S-box
- The round counter i is XORed with bits k19k18k17k16k15
Security Considerations
Despite its lightweight design, PRESENT offers reasonable security for its target applications:
- Designed to resist differential and linear cryptanalysis
- Best attacks on full PRESENT-80 are still computational infeasible
- Reduced-round variants have been analyzed extensively
- The 128-bit key version provides additional security margin
- Side-channel attacks are a concern in practical implementations
- Not recommended for high-security applications where AES can be used
Applications and Comparisons
PRESENT is used in various lightweight applications and compares favorably to other lightweight ciphers:
- RFID tags and smart cards
- Wireless sensor networks
- Internet of Things (IoT) devices
- Industrial control systems with limited resources
- Smaller hardware footprint than SIMON, SPECK, and LEA
- Standardized in ISO/IEC 29192-2:2019 for lightweight cryptography
- Complementary to other lightweight ciphers like CLEFIA and SIMON
PRESENT Animation
Visual representation of the PRESENT encryption process
PRESENT Animation
PRESENT Visualization
Round 1/5 - AddRoundKey
XORing the state with the round key
Round Structure
PRESENT Simulator
Try encrypting and decrypting with PRESENT
PRESENT Simulator
PRESENT Encryption Simulator
PRESENT Implementation
JavaScript implementation of the PRESENT algorithm
PRESENT Implementation
// PRESENT Encryption Algorithm Implementation in JavaScript
/**
* PRESENT block cipher implementation
*
* This is a JavaScript implementation of the PRESENT lightweight block cipher.
* PRESENT is a 64-bit block cipher with either 80-bit or 128-bit keys.
*
* Reference: "PRESENT: An Ultra-Lightweight Block Cipher" by
* Bogdanov, Knudsen, Leander, Paar, Poschmann, Robshaw, Seurin, and Vikkelsoe
*/
// S-box for substitution
const SBOX = [0xC, 0x5, 0x6, 0xB, 0x9, 0x0, 0xA, 0xD, 0x3, 0xE, 0xF, 0x8, 0x4, 0x7, 0x1, 0x2];
// Inverse S-box for decryption
const INVERSE_SBOX = [0x5, 0xE, 0xF, 0x8, 0xC, 0x1, 0x2, 0xD, 0xB, 0x4, 0x6, 0x3, 0x0, 0x7, 0x9, 0xA];
// Permutation table for bit permutation
const PBOX = [
0, 16, 32, 48, 1, 17, 33, 49, 2, 18, 34, 50, 3, 19, 35, 51,
4, 20, 36, 52, 5, 21, 37, 53, 6, 22, 38, 54, 7, 23, 39, 55,
8, 24, 40, 56, 9, 25, 41, 57, 10, 26, 42, 58, 11, 27, 43, 59,
12, 28, 44, 60, 13, 29, 45, 61, 14, 30, 46, 62, 15, 31, 47, 63
];
// Inverse permutation table for decryption
const INVERSE_PBOX = Array(64).fill(0);
for (let i = 0; i < 64; i++) {
INVERSE_PBOX[PBOX[i]] = i;
}
/**
* Convert a hexadecimal string to a BigInt
* @param {string} hex - Hexadecimal string
* @returns {BigInt} - BigInt representation
*/
function hexToBigInt(hex) {
return BigInt(`0x${hex}`);
}
/**
* Convert a BigInt to a hexadecimal string with specified length
* @param {BigInt} num - BigInt number
* @param {number} length - Desired length of the hex string
* @returns {string} - Hexadecimal string
*/
function bigIntToHex(num, length) {
return num.toString(16).padStart(length, '0');
}
/**
* Apply the S-box substitution to a 4-bit value
* @param {number} nibble - 4-bit value to substitute
* @returns {number} - Substituted value
*/
function applySbox(nibble) {
return SBOX[nibble];
}
/**
* Apply the inverse S-box substitution to a 4-bit value
* @param {number} nibble - 4-bit value to substitute
* @returns {number} - Substituted value
*/
function applyInverseSbox(nibble) {
return INVERSE_SBOX[nibble];
}
/**
* Apply the S-box layer to the entire 64-bit state
* @param {BigInt} state - 64-bit state
* @returns {BigInt} - State after S-box substitution
*/
function sBoxLayer(state) {
let result = BigInt(0);
// Process each 4-bit nibble
for (let i = 0; i < 16; i++) {
// Extract the nibble
const nibble = Number((state >> BigInt(i * 4)) & BigInt(0xF));
// Apply S-box and insert back
result |= BigInt(applySbox(nibble)) << BigInt(i * 4);
}
return result;
}
/**
* Apply the inverse S-box layer to the entire 64-bit state
* @param {BigInt} state - 64-bit state
* @returns {BigInt} - State after inverse S-box substitution
*/
function inverseSBoxLayer(state) {
let result = BigInt(0);
// Process each 4-bit nibble
for (let i = 0; i < 16; i++) {
// Extract the nibble
const nibble = Number((state >> BigInt(i * 4)) & BigInt(0xF));
// Apply inverse S-box and insert back
result |= BigInt(applyInverseSbox(nibble)) << BigInt(i * 4);
}
return result;
}
/**
* Apply the permutation layer to the 64-bit state
* @param {BigInt} state - 64-bit state
* @returns {BigInt} - State after bit permutation
*/
function pLayer(state) {
let result = BigInt(0);
// Permute each bit according to the P-box
for (let i = 0; i < 64; i++) {
// Extract the bit
const bit = (state >> BigInt(i)) & BigInt(1);
// Place the bit in its new position
result |= bit << BigInt(PBOX[i]);
}
return result;
}
/**
* Apply the inverse permutation layer to the 64-bit state
* @param {BigInt} state - 64-bit state
* @returns {BigInt} - State after inverse bit permutation
*/
function inversePLayer(state) {
let result = BigInt(0);
// Permute each bit according to the inverse P-box
for (let i = 0; i < 64; i++) {
// Extract the bit
const bit = (state >> BigInt(i)) & BigInt(1);
// Place the bit in its new position
result |= bit << BigInt(INVERSE_PBOX[i]);
}
return result;
}
/**
* Generate round keys for PRESENT-80
* @param {BigInt} key - 80-bit key
* @param {number} rounds - Number of rounds
* @returns {Array<BigInt>} - Array of round keys
*/
function generateRoundKeys80(key, rounds) {
const roundKeys = [];
let keyState = key;
// Generate keys for each round
for (let i = 0; i < rounds + 1; i++) {
// The round key is the leftmost 64 bits of the key state
roundKeys.push(keyState >> BigInt(16));
// Update the key state according to the key schedule
// 1. Rotate the key state to the left by 61 bits
keyState = ((keyState << BigInt(61)) | (keyState >> BigInt(19))) & ((BigInt(1) << BigInt(80)) - BigInt(1));
// 2. Apply S-box to the leftmost 4 bits
const leftmost = Number(keyState >> BigInt(76)) & 0xF;
const sBoxed = applySbox(leftmost);
keyState = (keyState & ~(BigInt(0xF) << BigInt(76))) | (BigInt(sBoxed) << BigInt(76));
// 3. XOR the round counter into bits 19-15
keyState ^= BigInt(i + 1) << BigInt(15);
}
return roundKeys;
}
/**
* Generate round keys for PRESENT-128
* @param {BigInt} key - 128-bit key
* @param {number} rounds - Number of rounds
* @returns {Array<BigInt>} - Array of round keys
*/
function generateRoundKeys128(key, rounds) {
const roundKeys = [];
let keyState = key;
// Generate keys for each round
for (let i = 0; i < rounds + 1; i++) {
// The round key is the leftmost 64 bits of the key state
roundKeys.push(keyState >> BigInt(64));
// Update the key state according to the key schedule
// 1. Rotate the key state to the left by 61 bits
keyState = ((keyState << BigInt(61)) | (keyState >> BigInt(67))) & ((BigInt(1) << BigInt(128)) - BigInt(1));
// 2. Apply S-box to the two leftmost 4-bit words
let leftmost = Number(keyState >> BigInt(124)) & 0xF;
let sBoxed = applySbox(leftmost);
keyState = (keyState & ~(BigInt(0xF) << BigInt(124))) | (BigInt(sBoxed) << BigInt(124));
leftmost = Number(keyState >> BigInt(120)) & 0xF;
sBoxed = applySbox(leftmost);
keyState = (keyState & ~(BigInt(0xF) << BigInt(120))) | (BigInt(sBoxed) << BigInt(120));
// 3. XOR the round counter into bits 66-62
keyState ^= BigInt(i + 1) << BigInt(62);
}
return roundKeys;
}
/**
* Encrypt a 64-bit block using PRESENT
* @param {string} blockHex - 64-bit block as a hex string
* @param {string} keyHex - Key as a hex string (80 or 128 bits)
* @param {number} rounds - Number of rounds (default: 31)
* @returns {string} - Encrypted block as a hex string
*/
function encryptBlock(blockHex, keyHex, rounds = 31) {
// Convert hex strings to BigInt
const block = hexToBigInt(blockHex);
const key = hexToBigInt(keyHex);
// Generate round keys based on key size
const roundKeys = keyHex.length <= 20
? generateRoundKeys80(key, rounds)
: generateRoundKeys128(key, rounds);
let state = block;
// Apply rounds
for (let i = 0; i < rounds; i++) {
// Add round key (XOR with round key)
state ^= roundKeys[i];
// Apply S-box layer
state = sBoxLayer(state);
// Apply permutation layer (except in the last round)
if (i < rounds - 1) {
state = pLayer(state);
}
}
// Apply the last round key
state ^= roundKeys[rounds];
// Convert back to hex string
return bigIntToHex(state, 16);
}
/**
* Decrypt a 64-bit block using PRESENT
* @param {string} blockHex - 64-bit block as a hex string
* @param {string} keyHex - Key as a hex string (80 or 128 bits)
* @param {number} rounds - Number of rounds (default: 31)
* @returns {string} - Decrypted block as a hex string
*/
function decryptBlock(blockHex, keyHex, rounds = 31) {
// Convert hex strings to BigInt
const block = hexToBigInt(blockHex);
const key = hexToBigInt(keyHex);
// Generate round keys based on key size
const roundKeys = keyHex.length <= 20
? generateRoundKeys80(key, rounds)
: generateRoundKeys128(key, rounds);
let state = block;
// Apply the last round key first
state ^= roundKeys[rounds];
// Apply rounds in reverse
for (let i = rounds - 1; i >= 0; i--) {
// Apply inverse permutation layer (except in the first round of decryption)
if (i < rounds - 1) {
state = inversePLayer(state);
}
// Apply inverse S-box layer
state = inverseSBoxLayer(state);
// Add round key (XOR with round key)
state ^= roundKeys[i];
}
// Convert back to hex string
return bigIntToHex(state, 16);
}
/**
* Encrypt a message using PRESENT in ECB mode
* @param {string} message - Message to encrypt
* @param {string} keyHex - Key as a hex string (80 or 128 bits)
* @param {number} rounds - Number of rounds (default: 31)
* @param {boolean} isHex - Whether the message is already in hex format
* @returns {string} - Encrypted message as a hex string
*/
function encryptPRESENT(message, keyHex, rounds = 31, isHex = false) {
// Convert message to hex if needed
const messageHex = isHex ? message : stringToHex(message);
// Pad the message to a multiple of 16 hex characters (64 bits)
const paddedMessage = padHex(messageHex);
let ciphertext = '';
// Encrypt each 64-bit (16 hex chars) block
for (let i = 0; i < paddedMessage.length; i += 16) {
const block = paddedMessage.substr(i, 16);
const encryptedBlock = encryptBlock(block, keyHex, rounds);
ciphertext += encryptedBlock;
}
return ciphertext;
}
/**
* Decrypt a message using PRESENT in ECB mode
* @param {string} ciphertext - Ciphertext as a hex string
* @param {string} keyHex - Key as a hex string (80 or 128 bits)
* @param {number} rounds - Number of rounds (default: 31)
* @param {boolean} toHex - Whether to return the result as hex or convert to string
* @returns {string} - Decrypted message
*/
function decryptPRESENT(ciphertext, keyHex, rounds = 31, toHex = false) {
let plaintext = '';
// Decrypt each 64-bit (16 hex chars) block
for (let i = 0; i < ciphertext.length; i += 16) {
const block = ciphertext.substr(i, 16);
const decryptedBlock = decryptBlock(block, keyHex, rounds);
plaintext += decryptedBlock;
}
// Convert from hex to string if requested
return toHex ? plaintext : hexToString(plaintext);
}
/**
* Convert a string to a hex string
* @param {string} str - Input string
* @returns {string} - Hex string
*/
function stringToHex(str) {
let hex = '';
for (let i = 0; i < str.length; i++) {
const charCode = str.charCodeAt(i);
hex += charCode.toString(16).padStart(2, '0');
}
return hex;
}
/**
* Convert a hex string to a string
* @param {string} hex - Hex string
* @returns {string} - Decoded string
*/
function hexToString(hex) {
let str = '';
for (let i = 0; i < hex.length; i += 2) {
const charCode = parseInt(hex.substr(i, 2), 16);
if (charCode) {
str += String.fromCharCode(charCode);
}
}
return str;
}
/**
* Pad a hex string to a multiple of 16 characters (64 bits)
* @param {string} hex - Hex string to pad
* @returns {string} - Padded hex string
*/
function padHex(hex) {
const blockSize = 16; // 64 bits = 16 hex characters
const paddingLength = blockSize - (hex.length % blockSize);
if (paddingLength < blockSize) {
return hex + '0'.repeat(paddingLength);
}
return hex;
}
// Export the functions
module.exports = {
encryptPRESENT,
decryptPRESENT,
encryptBlock,
decryptBlock,
stringToHex,
hexToString
};
Implementation Notes
- This is a pure JavaScript implementation of PRESENT for educational purposes.
- Uses BigInt for handling 64-bit blocks and 80/128-bit keys.
- Implements both PRESENT-80 and PRESENT-128 variants.
- The implementation follows the original specification by Bogdanov et al.
- For simplicity, this uses ECB mode which is not recommended for secure applications.
- In a real-world scenario, you would use a more secure mode of operation like CBC or CTR.
- The standard number of rounds is 31, but can be reduced for performance at the cost of security.
- For production use, consider using established cryptographic libraries.