or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

aead-ciphers.mdasymmetric-cryptography.mdhash-functions.mdindex.mdkey-derivation.mdkey-serialization.mdmessage-authentication.mdsymmetric-ciphers.mdsymmetric-encryption.mdtwo-factor-auth.mdutilities.mdx509-certificates.md

symmetric-ciphers.mddocs/

0

# Symmetric Ciphers

1

2

Low-level symmetric encryption algorithms and cipher modes. These are building blocks for custom cryptographic protocols - for most applications, use high-level APIs like Fernet or AEAD ciphers instead.

3

4

## Core Imports

5

6

```python

7

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

8

```

9

10

## Capabilities

11

12

### Cipher Interface

13

14

The `Cipher` class provides a consistent interface for all symmetric ciphers.

15

16

```python { .api }

17

class Cipher:

18

def __init__(self, algorithm, mode, backend=None):

19

"""

20

Create cipher with algorithm and mode.

21

22

Args:

23

algorithm: Cipher algorithm (AES(), ChaCha20(), etc.)

24

mode: Cipher mode (CBC(), CTR(), GCM(), etc.)

25

backend: Cryptographic backend (usually None)

26

"""

27

28

def encryptor(self) -> CipherContext:

29

"""

30

Create encryption context.

31

32

Returns:

33

CipherContext: Context for encryption operations

34

"""

35

36

def decryptor(self) -> CipherContext:

37

"""

38

Create decryption context.

39

40

Returns:

41

CipherContext: Context for decryption operations

42

"""

43

44

class CipherContext:

45

def update(self, data: bytes) -> bytes:

46

"""

47

Process data through cipher.

48

49

Args:

50

data (bytes): Data to encrypt/decrypt

51

52

Returns:

53

bytes: Processed data

54

"""

55

56

def finalize(self) -> bytes:

57

"""

58

Finalize cipher operation.

59

60

Returns:

61

bytes: Final processed data

62

"""

63

```

64

65

### Cipher Algorithms

66

67

```python { .api }

68

class AES:

69

def __init__(self, key: bytes):

70

"""

71

AES algorithm with 128, 192, or 256-bit key.

72

73

Args:

74

key (bytes): 16, 24, or 32-byte key

75

"""

76

77

@property

78

def block_size(self) -> int:

79

"""AES block size (16 bytes)"""

80

return 16

81

82

class ChaCha20:

83

def __init__(self, key: bytes, nonce: bytes):

84

"""

85

ChaCha20 stream cipher.

86

87

Args:

88

key (bytes): 32-byte key

89

nonce (bytes): 16-byte nonce

90

"""

91

92

class Camellia:

93

def __init__(self, key: bytes):

94

"""

95

Camellia block cipher (128, 192, or 256-bit key).

96

97

Args:

98

key (bytes): 16, 24, or 32-byte key

99

"""

100

101

@property

102

def block_size(self) -> int:

103

return 16

104

105

class SM4:

106

def __init__(self, key: bytes):

107

"""

108

SM4 block cipher (Chinese national standard).

109

110

Args:

111

key (bytes): 16-byte key

112

"""

113

114

@property

115

def block_size(self) -> int:

116

return 16

117

```

118

119

### Cipher Modes

120

121

```python { .api }

122

class ECB:

123

"""

124

Electronic Codebook mode (insecure - don't use).

125

Each block encrypted independently.

126

"""

127

128

class CBC:

129

def __init__(self, initialization_vector: bytes):

130

"""

131

Cipher Block Chaining mode.

132

133

Args:

134

initialization_vector (bytes): IV same size as block size

135

"""

136

137

class CTR:

138

def __init__(self, nonce: bytes):

139

"""

140

Counter mode (stream cipher mode).

141

142

Args:

143

nonce (bytes): Nonce for counter initialization

144

"""

145

146

class CFB:

147

def __init__(self, initialization_vector: bytes):

148

"""

149

Cipher Feedback mode.

150

151

Args:

152

initialization_vector (bytes): IV same size as block size

153

"""

154

155

class OFB:

156

def __init__(self, initialization_vector: bytes):

157

"""

158

Output Feedback mode.

159

160

Args:

161

initialization_vector (bytes): IV same size as block size

162

"""

163

164

class GCM:

165

def __init__(self, initialization_vector: bytes, tag: bytes = None, min_tag_length: int = 16):

166

"""

167

Galois/Counter Mode (authenticated encryption).

168

169

Args:

170

initialization_vector (bytes): IV (96 bits recommended)

171

tag (bytes, optional): Authentication tag for decryption

172

min_tag_length (int): Minimum tag length (4-16 bytes)

173

"""

174

```

175

176

## Usage Examples

177

178

### Basic AES-CBC Encryption

179

180

```python

181

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

182

from cryptography.hazmat.primitives import padding

183

import os

184

185

# Generate key and IV

186

key = os.urandom(32) # 256-bit key

187

iv = os.urandom(16) # 128-bit IV

188

189

# Create cipher

190

cipher = Cipher(algorithms.AES(key), modes.CBC(iv))

191

192

# Encrypt data

193

plaintext = b"Secret message that needs padding for block cipher"

194

195

# Pad data to block size

196

padder = padding.PKCS7(128).padder() # 128-bit block size

197

padded_data = padder.update(plaintext)

198

padded_data += padder.finalize()

199

200

# Encrypt

201

encryptor = cipher.encryptor()

202

ciphertext = encryptor.update(padded_data) + encryptor.finalize()

203

204

print(f"Encrypted: {ciphertext.hex()}")

205

206

# Decrypt

207

decryptor = cipher.decryptor()

208

padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize()

209

210

# Remove padding

211

unpadder = padding.PKCS7(128).unpadder()

212

plaintext_recovered = unpadder.update(padded_plaintext)

213

plaintext_recovered += unpadder.finalize()

214

215

print(f"Decrypted: {plaintext_recovered}")

216

```

217

218

### AES-CTR Mode (Stream Cipher)

219

220

```python

221

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

222

import os

223

224

# CTR mode doesn't require padding

225

key = os.urandom(32)

226

nonce = os.urandom(16)

227

228

cipher = Cipher(algorithms.AES(key), modes.CTR(nonce))

229

230

plaintext = b"This message doesn't need padding in CTR mode"

231

232

# Encrypt

233

encryptor = cipher.encryptor()

234

ciphertext = encryptor.update(plaintext) + encryptor.finalize()

235

236

# Decrypt

237

decryptor = cipher.decryptor()

238

decrypted_text = decryptor.update(ciphertext) + decryptor.finalize()

239

240

print(f"Original: {plaintext}")

241

print(f"Decrypted: {decrypted_text}")

242

print(f"Match: {plaintext == decrypted_text}")

243

```

244

245

### ChaCha20 Stream Cipher

246

247

```python

248

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms

249

import os

250

251

# ChaCha20 is a stream cipher

252

key = os.urandom(32) # 256-bit key

253

nonce = os.urandom(16) # 128-bit nonce

254

255

cipher = Cipher(algorithms.ChaCha20(key, nonce), mode=None)

256

257

message = b"Stream cipher message - no padding needed"

258

259

# Encrypt

260

encryptor = cipher.encryptor()

261

ciphertext = encryptor.update(message) + encryptor.finalize()

262

263

# Decrypt

264

decryptor = cipher.decryptor()

265

plaintext = decryptor.update(ciphertext) + decryptor.finalize()

266

267

print(f"ChaCha20 encryption successful: {message == plaintext}")

268

```

269

270

### File Encryption with AES-CBC

271

272

```python

273

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

274

from cryptography.hazmat.primitives import padding

275

import os

276

277

def encrypt_file(input_path, output_path, key):

278

"""Encrypt file with AES-CBC"""

279

iv = os.urandom(16)

280

cipher = Cipher(algorithms.AES(key), modes.CBC(iv))

281

encryptor = cipher.encryptor()

282

283

padder = padding.PKCS7(128).padder()

284

285

with open(input_path, 'rb') as infile, open(output_path, 'wb') as outfile:

286

# Write IV first

287

outfile.write(iv)

288

289

# Encrypt file in chunks with padding

290

while True:

291

chunk = infile.read(8192)

292

if not chunk:

293

# Final chunk with padding

294

padded_chunk = padder.finalize()

295

if padded_chunk:

296

outfile.write(encryptor.update(padded_chunk))

297

break

298

299

if len(chunk) < 8192:

300

# Last chunk - apply padding

301

padded_chunk = padder.update(chunk) + padder.finalize()

302

outfile.write(encryptor.update(padded_chunk))

303

break

304

else:

305

# Full chunk - no padding yet

306

padded_chunk = padder.update(chunk)

307

outfile.write(encryptor.update(padded_chunk))

308

309

# Finalize encryption

310

outfile.write(encryptor.finalize())

311

312

def decrypt_file(input_path, output_path, key):

313

"""Decrypt file encrypted with AES-CBC"""

314

with open(input_path, 'rb') as infile:

315

# Read IV

316

iv = infile.read(16)

317

318

cipher = Cipher(algorithms.AES(key), modes.CBC(iv))

319

decryptor = cipher.decryptor()

320

321

# Read and decrypt remaining data

322

ciphertext = infile.read()

323

324

padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize()

325

326

# Remove padding

327

unpadder = padding.PKCS7(128).unpadder()

328

plaintext = unpadder.update(padded_plaintext) + unpadder.finalize()

329

330

with open(output_path, 'wb') as outfile:

331

outfile.write(plaintext)

332

333

# Usage

334

key = os.urandom(32)

335

336

# Create test file

337

with open('test.txt', 'w') as f:

338

f.write("This is a test file for encryption\n" * 100)

339

340

# Encrypt file

341

encrypt_file('test.txt', 'test.txt.enc', key)

342

print("File encrypted")

343

344

# Decrypt file

345

decrypt_file('test.txt.enc', 'test_decrypted.txt', key)

346

print("File decrypted")

347

348

# Verify

349

with open('test.txt', 'rb') as f1, open('test_decrypted.txt', 'rb') as f2:

350

print(f"Files match: {f1.read() == f2.read()}")

351

```

352

353

## Security Considerations

354

355

- **Mode Selection**: Never use ECB mode for real applications

356

- **IV/Nonce Management**: Always use random, unique IVs/nonces

357

- **Padding**: Required for block ciphers with non-block-sized data

358

- **Authentication**: Low-level ciphers don't provide authentication - use AEAD instead

359

- **Key Management**: Use proper key derivation, never reuse keys inappropriately

360

- **Stream Cipher Risks**: Never reuse key/nonce pairs with stream ciphers

361

- **High-Level APIs**: Prefer Fernet or AEAD ciphers for most applications