붉은별 사용자용체계

pilsung 암호화 알고리즘

06-09-2022

Pilsung Desktop Image 1

Red Star OS is the Linux based OS created by the glorious Supreme Leader and his exquisite Information technology development team at the Korean Computer Center. Version 3.0 was released in the summer of 2012 and subsequently leaked internationally by Zammis Clark (also known as wack0).

A particularly interesting detail is the addition of kernel modules that are specifically designed to handle cryptographic operations. They allow the operating system to perform encryption, decryption, and other cryptographic functions “securely”.

By having these modules integrated into the kernel, the OS ensures that cryptographic operations are efficiently executed within the operating system itself, providing a higher level of security and performance. In this case, security refers to protection from user tampering, this is also thanks to the protection offered by SELinux.

Pilsung Desktop Image 2

Red Star OS includes cryptographic kernel modules: Jipsam1, Jipsam2 and Pilsung. These ciphers are all based on AES, but include some modifications.

The jipsam modules are present in Red Star OS 2.0, whereas Pilsung is present only in Red Star OS 3.0.

The most complex of these is Pilsung, which uses key-dependent S-Boxes and permutations. In particular, the ShiftRows operation of AES is replaced with a pseudo-random permutation, which is selected based on the round key.

In North Korean (and more broadly in Korean martial arts context), “pilsung” (필승) means “certain victory” or “sure victory”. It is often used as a motivational term to inspire confidence and determination, signifying an unwavering belief in achieving victory.

Downloadable kernel module: sha1: a4470a1e0cf25867b827f686f30405cce67cf22d - sha256 809274f702c92c1efcf0cf960cabf3e43cc6ec261ff83b6a73ebacbd27527472 - sha512 eea8bbe56662a3b978e83e8a29bb310aa9896777a4b5393388dcf216ba78438e3e80bfaa239ff74a44e0242ff0e34cf653f78ad56856f8ce479043c9ad50ae1d

Pilsung Desktop Image 3

package pilsung

// Multiply by x modulo x^8 + x^4 + x^3 + x + 1
func xtime(b byte) byte {
	if b&0x80 != 0 {
		return (b << 1) ^ 0x1b
	}
	return b << 1
}

// GF(2^8) generic multiplication, double-and-add
func multiply(a byte, b byte) byte {
	var c byte = 0x0
	for i := 0; i < 8; i++ {
		if ((b >> i) & 1) != 0 {
			c ^= a
		}
		a = xtime(a)
	}
	return c
}

// Rotate Left
func rotateLeft(x byte, c int) byte {
	return (x << c % 8) | (x >> (8 - c) % 8)
}

// Unmodified AES S-box
func subByte(x0 byte) byte {
	x1 := multiply(x0, x0)    // x^2
	x2 := multiply(x1, x0)    // x^3
	x3 := multiply(x2, x2)    // x^6
	x4 := multiply(x3, x3)    // x^12
	x5 := multiply(x4, x2)    // x^15
	x6 := multiply(x5, x5)    // x^30
	x7 := multiply(x6, x6)    // x^60
	x8 := multiply(x7, x2)    // x^63
	x9 := multiply(x8, x8)    // x^126
	x10 := multiply(x9, x0)   // x^127
	x11 := multiply(x10, x10) // x^254 = x^-1
	return x11 ^ rotateLeft(x11, 1) ^ rotateLeft(x11, 2) ^
		rotateLeft(x11, 3) ^ rotateLeft(x11, 4) ^ 0x63
}

// Rotate the word
func rotWord(w [4]byte) [4]byte {
	t := w[0]
	w[0] = w[1]
	w[1] = w[2]
	w[2] = w[3]
	w[3] = t
	return w
}

// Subtract the word
func subWord(w [4]byte) [4]byte {
	for i := 0; i < 4; i++ {
		w[i] = subByte(w[i])
	}
	return w
}

// Sort array p of size n according to 0-1 array s
// Assumes s has n / 2 zeros and n / 2 ones
func getOne(s []byte, p []byte) []byte {
	a := 0
	b := 0
	buf := p
	for i := 0; i < len(p); i++ {
		if s[i] != 0x00 {
			buf[len(p)/2+a] = p[i]
			a++
		} else {
			buf[b] = p[i]
			b++
		}
	}
	return p
}

type blockT [4][4]byte

// Mix block columns
func (block *blockT) mixColumns() {
	for i := 0; i < 4; i++ {
		c0 := block[i][0]
		c1 := block[i][1]
		c2 := block[i][2]
		c3 := block[i][3]
		block[i][0] = xtime(c0^c1) ^ c1 ^ c2 ^ c3
		block[i][1] = c0 ^ xtime(c1^c2) ^ c2 ^ c3
		block[i][2] = c0 ^ c1 ^ xtime(c2^c3) ^ c3
		block[i][3] = xtime(c0^c3) ^ c0 ^ c1 ^ c2
	}
}

// Add round key to block
func (block *blockT) addRoundKey(k [4][4]byte) {
	for i := 0; i < 4; i++ {
		for j := 0; j < 4; j++ {
			block[i][j] ^= k[i][j]
		}
	}
}

// Sub bytes to block
func (block *blockT) subBytes(ctx *pilsungCtx, round int) {
	for i := 0; i < 4; i++ {
		for j := 0; j < 4; j++ {
			block[i][j] = ctx.sboxes[round][i][j][block[i][j]]
		}
	}
}

// Shift block rows
func (block *blockT) shiftRows(ctx *pilsungCtx, round int) {
	var copy [16]byte
	for i := 0; i < 4; i++ {
		for j := 0; j < 4; j++ {
			copy[i*4+j] = block[i][j]
		}
	}
	for i := 0; i < 4; i++ {
		for j := 0; j < 4; j++ {
			block[i][j] = copy[ctx.pboxes[round][i*4+j]]
		}
	}
}

Pilsung Desktop Image 4

package pilsung

// Pilsung Context
type pilsungCtx struct {
	sboxXorConstant     byte                // 3
	sboxes              [30][4][4][256]byte // S-Boxes
	pboxes              [30][16]byte        // P-Boxes
	currentPermutation8 [8]byte             // used for temporary storage
	eKey                [240]byte           // AES scheduled key
}

// Permute the bits of inputMask according to the computed permutation
func (ctx *pilsungCtx) getPeSb(inputMask byte) byte {
	var x byte = 0x0
	for i := 0; i < 8; i++ {
		if ((1 << i) & inputMask) != 0x0 {
			x |= 1 << ctx.currentPermutation8[i]
		}
	}
	return x ^ ctx.sboxXorConstant
}

// Generate a random permutation of [0:7] at ctx.currentPermutation8
func (ctx *pilsungCtx) getP8forSEnc(inputMask byte) {
	var (
		coinflips [24]byte
		v0        byte = treeInteger8[inputMask%70]
		v1        byte = (treeInteger4[(inputMask&0x0F)%6] << 0) | (treeInteger4[(inputMask>>4)%6] << 4)
	)

	for i := 0; i < 8; i++ {
		coinflips[i] = (v0 >> (7 - i)) & 1
	}
	for i := 0; i < 8; i++ {
		coinflips[i+8] = (v1 >> (7 - i)) & 1
	}

	for i := 0; i < 8; i += 2 {
		if (inputMask & (3 << i)) != 0x0 {
			coinflips[16+i+0] = 1
			coinflips[16+i+1] = 0
		} else {
			coinflips[16+i+0] = 0
			coinflips[16+i+1] = 1
		}
	}

	// Initialize permutation with identity
	for i := 0; i < 8; i++ {
		ctx.currentPermutation8[i] = byte(i)
	}

	// Iterative version of the Rao-Sandelius shuffle
	for i := 0; i < 3; i++ {
		bins := 1 << i
		size := 1 << (3 - i)
		for j := 0; j < bins; j++ {
			s := coinflips[i*8 : i*8+7]
			p := ctx.currentPermutation8[j*size : j*size+size-1]
			copy(ctx.currentPermutation8[:], getOne(s, p)[:])
		}
	}
}

// Generate a random permutation of [0:15] at output
func getP16Enc(input [16]byte) [16]byte {
	var (
		coinflips [64]byte
		output    [16]byte
	)

	// coin flips for first level
	for i := 0; i < 4; i++ {
		v0 := treeInteger4[(input[i]^input[i+4])%6]
		for j := 0; j < 4; j++ {
			coinflips[4*i+j] = (v0 >> (3 - j)) & 1
		}
	}

	// coin flips for second level
	for i := 0; i < 8; i++ {
		if ((input[i] >> i) & 1) != 0 {
			coinflips[16+2*i+0] = 1
			coinflips[16+2*i+1] = 0
		} else {
			coinflips[16+2*i+0] = 0
			coinflips[16+2*i+1] = 1
		}
	}

	// coin flips for third level
	for i := 0; i < 4; i++ {
		v1 := treeInteger4[(input[i+8]^input[i+12])%6]
		for j := 0; j < 4; j++ {
			coinflips[32+4*i+j] = (v1 >> (3 - j)) & 1
		}
	}

	// coin flips for fourth level
	for i := 0; i < 8; i++ {
		if ((input[8+i] >> i) & 1) != 0 {
			coinflips[48+2*i+0] = 1
			coinflips[48+2*i+1] = 0
		} else {
			coinflips[48+2*i+0] = 0
			coinflips[48+2*i+1] = 1
		}
	}

	// Initialize permutation with identity
	for i := 0; i < 16; i++ {
		output[i] = byte(i)
	}

	// Iterative version of the Rao-Sandelius shuffle
	for i := 0; i < 4; i++ {
		bins := 1 << i       // number of subgroups
		size := 1 << (4 - i) // size of each subgroup
		for j := 0; j < bins; j++ {
			s := coinflips[i*16 : i*16+15]
			p := output[j*size : j*size+size-1]
			copy(output[:], getOne(s, p)[:])
		}
	}
	return output
}

// Generate all necessary S-boxes
func (ctx *pilsungCtx) getVSboxAll() {
	for rounds := 1; rounds < gRoundCnt; rounds++ {
		for i := 0; i < 4; i++ {
			for j := 0; j < 4; j++ {
				ctx.getP8forSEnc(ctx.eKey[j+4*i+16*rounds])
				for k := 0; k < 256; k++ {
					ctx.sboxes[rounds][i][j][k] = ctx.getPeSb(subByte(byte(k)))
				}
			}
		}
	}
}

// Generate all necessary P-boxes
func (ctx *pilsungCtx) getVPboxAll() {
	for rounds := 1; rounds < gRoundCnt; rounds++ {
		var roundScheduledKey [16]byte
		copy(roundScheduledKey[:], ctx.eKey[16*rounds:16*rounds+15])
		ctx.pboxes[rounds] = getP16Enc(roundScheduledKey)
	}
}

// Initialize the xor constant
func (ctx *pilsungCtx) initVar() {
	ctx.sboxXorConstant = xorConstant
}

// Generate the encryption permutations
func (ctx *pilsungCtx) genEncPerm() {
	ctx.initVar()
	ctx.getVSboxAll()
	ctx.getVPboxAll()
}

Pilsung Desktop Image 5

package pilsung

import "crypto/sha1"

// Pilsung tweaked hashing function
func shaSign(in []byte) []byte {

	var out = []byte{}
	for i := 0; i < len(in); i += 16 {

		// Pass 16 bytes through SHA-1
		blocklen := 16
		if i+blocklen > len(in) {
			blocklen = len(in) - i
		}
		digest := sha1.Sum(in[i : i+blocklen])

		// Pilsung's tweak to SHA-1
		for i := 0; i < 20; i += 4 {
			digest[i+3] ^= 0xFF
		}
		for i := 0; i < blocklen; i++ {
			out = append(out, digest[i])
		}
	}

	return out
}

// This is the KDF
func shaKey(in []byte, outlen int) []byte {
	var out = []byte{}
	if len(in) <= outlen {
		return shaSign(in)
	}

	// Limit the dimension of the key
	maxlength := 256
	if len(in) < 256 {
		maxlength = len(in)
	}

	// Pass the chunk though the custom hash function
	for i := 0; i < maxlength; i += 32 {
		chunklen := 32
		if i+chunklen > maxlength {
			chunklen = maxlength - i
		}
		buffer := shaSign(in[i : i+chunklen])
		for j := 0; j < chunklen; j++ {
			out[i+j] = in[i+j] ^ buffer[j]
		}
	}

	return out
}

// This is the AES key schedule
func (ctx *pilsungCtx) expandRoundkey(k []byte, Nr int) {
	var (
		Nk   int  = Nr - 6 // 5
		rcon byte = 1
	)

	for i := 0; i < Nk; i++ {
		for j := 0; j < 4; j++ {
			ctx.eKey[i*Nk+j] = k[4*i+j]
		}
	}

	for i := Nk; i < 4*(Nr+1); i++ {
		var temp [4]byte
		for j := 0; j < 4; j++ {
			temp[j] = ctx.eKey[(i-1)*Nk+j]
		}
		if i%Nk == 0 {
			temp = rotWord(temp)
			temp = subWord(temp)
			temp[0] ^= rcon
			rcon = xtime(rcon)
		} else if Nk > 6 && i%Nk == 4 {
			temp = subWord(temp)
		}
		for j := 0; j < 4; j++ {
			ctx.eKey[i*Nk+j] = ctx.eKey[(i-Nk)*Nk+j] ^ temp[j]
		}
	}
}

// Key schedule wrapper
func (ctx *pilsungCtx) expandKey(k []byte) {
	derived := shaKey(k, gCryptoKeyLen)
	ctx.expandRoundkey(derived, gRoundCnt)
}

// Get the key for the current round in the right format
func (ctx *pilsungCtx) formatKey(round int) [4][4]byte {
	var output [4][4]byte
	for i := 0; i < 4; i++ {
		for j := 0; j < 4; j++ {
			output[i][j] = ctx.eKey[16*round+4*i+j]
		}
	}
	return output
}

Pilsung Desktop Image 6

package pilsung

const (
	gCryptoKeyLen = 32 // 256-bit key
	gRoundCnt     = 11 // 11 rounds
	xorConstant   = 3  // S-box Xor Constant
)

var (
	// All combinations of 4 bits in 8-bit words
	treeInteger8 = [70]byte{
		0x0F, 0x87, 0x47, 0x27, 0x17, 0xC3, 0x63, 0x33, 0x1B, 0xA3,
		0x53, 0x2B, 0x93, 0x4B, 0x8B, 0xE1, 0x71, 0x39, 0x1D, 0xB1,
		0x59, 0x2D, 0x99, 0x4D, 0x8D, 0xD1, 0x69, 0x35, 0xA9, 0x55,
		0x95, 0xC9, 0x65, 0xA5, 0xC5, 0xF0, 0x78, 0x3C, 0x1E, 0xB8,
		0x5C, 0x2E, 0x9C, 0x4E, 0x8E, 0xD8, 0x6C, 0x36, 0xAC, 0x56,
		0x96, 0xCC, 0x66, 0xA6, 0xC6, 0xE8, 0x74, 0x3A, 0xB4, 0x5A,
		0x9A, 0xD4, 0x6A, 0xAA, 0xCA, 0xE4, 0x72, 0xB2, 0xD2, 0xE2,
	}
	// All combinations of 2 bits in 4-bit words
	treeInteger4 = [6]byte{
		0x03, 0x09, 0x05, 0x0C, 0x06, 0x0A,
	}
)

// Export the Encryption function
func Encrypt(input [16]byte, key []byte) [16]byte {
	var (
		block  blockT
		ctx    pilsungCtx
		output [16]byte
	)

	// Set the key
	ctx.expandKey(key)
	ctx.genEncPerm()

	// Array to matrix
	for i := 0; i < 4; i++ {
		for j := 0; j < 4; j++ {
			block[i][j] = input[i*4+j]
		}
	}

	// First round
	block.addRoundKey(ctx.formatKey(0))

	// Middle rounds
	for round := 1; round < 10; round++ {
		block.subBytes(&ctx, round)
		block.shiftRows(&ctx, round)
		block.mixColumns()
		block.addRoundKey(ctx.formatKey(round))
	}

	// Last round
	block.subBytes(&ctx, 10)
	block.shiftRows(&ctx, 10)
	// No mixColumns
	block.addRoundKey(ctx.formatKey(10))

	// Matrix to array
	for i := 0; i < 4; i++ {
		for j := 0; j < 4; j++ {
			output[i+4+j] = block[i][j]
		}
	}

	return output
}

Pilsung Desktop Image 7

Pilsung is coded around the assumption that AES with actual random S-boxes is provably secure against large classes of attacks, given enough rounds.

An S-box (Substitution box) is a fundamental component in symmetric key algorithms, particularly in block ciphers. It performs substitution, a form of non-linear transformation, which is crucial for introducing complexity and confusion into the encryption process.

S-boxes provide non-linear transformations which are essential for cryptographic strength. This non-linearity helps in thwarting linear cryptanalysis, a method used to break ciphers.

Introduced by Claude Shannon, confusion is a cryptographic principle aimed at obscuring the relationship between the plaintext, the ciphertext, and the key. S-boxes achieve this by substituting input bits with output bits in a non-linear manner.

While more directly associated with P-boxes (Permutation boxes), S-boxes also contribute indirectly to diffusion by ensuring that small changes in the input lead to significant changes in the output.

An S-box takes a fixed number of input bits and transforms them into a fixed number of output bits.

The transformations are typically defined in a lookup table format, where each possible input value maps to a specific output value.

S-boxes can be static (fixed) or dynamic. Static S-boxes are predefined and do not change, whereas dynamic S-boxes can be generated based on certain parameters, such as the key.

The AES S-box output is derived from the multiplicative inverse over a finite field combined with an affine transformation. This construction ensures strong resistance to both linear and differential cryptanalysis.

Designing secure S-boxes is a complex task, as poorly designed S-boxes can introduce vulnerabilities. A well-designed S-box should have high non-linearity to resist linear attacks, good avalanche effect (a small change in the input results in a significant change in the output) and resistance to known cryptographic attacks.

Pilsung Desktop Image 8