Skip to content
CyberXplained

PRESENT Encryption

AdvancedSymmetric

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:

  1. The 80-bit key is stored in a key register K = k79k78...k0
  2. The round key consists of the 64 leftmost bits of the current key register: Ki = k79k78...k16
  3. 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

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Round 1/5 - AddRoundKey

XORing the state with the round key

Round Structure

Input
AddRoundKey
S-box
Permutation
Output
SlowFast

PRESENT Simulator

Try encrypting and decrypting with PRESENT

PRESENT Simulator

PRESENT Encryption Simulator

1 (Faster, Insecure)1631 (Standard)

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.