or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

compression.mdcore-io.mdfilesystem.mdhashing.mdindex.mdutilities.md

hashing.mddocs/

0

# Hashing and Security

1

2

This document covers Okio's cryptographic hashing operations and secure data handling capabilities. Okio provides built-in support for common hash functions and HMAC operations.

3

4

## Hash Functions Overview

5

6

Okio provides direct support for these cryptographic hash functions:

7

8

- **MD5**: 128-bit hash (deprecated for security, but still available)

9

- **SHA-1**: 160-bit hash (deprecated for security, but still available)

10

- **SHA-256**: 256-bit hash (recommended)

11

- **SHA-512**: 512-bit hash (recommended)

12

13

All hash functions are available on both `ByteString` and `Buffer` objects, plus HMAC variants.

14

15

## ByteString Hashing

16

17

All hash functions are available as methods on ByteString instances:

18

19

### Basic Hash Functions

20

21

```kotlin { .api }

22

expect open class ByteString {

23

// Cryptographic hash functions

24

fun md5(): ByteString

25

fun sha1(): ByteString

26

fun sha256(): ByteString

27

fun sha512(): ByteString

28

29

// HMAC operations

30

fun hmacSha1(key: ByteString): ByteString

31

fun hmacSha256(key: ByteString): ByteString

32

fun hmacSha512(key: ByteString): ByteString

33

}

34

```

35

36

### Usage Examples

37

38

```kotlin

39

// Basic hashing of text data

40

val data = "Hello, Okio Security!".encodeUtf8()

41

42

// Generate different hash types

43

val md5Hash = data.md5()

44

val sha1Hash = data.sha1()

45

val sha256Hash = data.sha256()

46

val sha512Hash = data.sha512()

47

48

println("Original: ${data.utf8()}")

49

println("MD5: ${md5Hash.hex()}")

50

println("SHA-1: ${sha1Hash.hex()}")

51

println("SHA-256: ${sha256Hash.hex()}")

52

println("SHA-512: ${sha512Hash.hex()}")

53

54

// HMAC operations with secret key

55

val secretKey = "my-secret-key".encodeUtf8()

56

val hmacSha256 = data.hmacSha256(secretKey)

57

val hmacSha512 = data.hmacSha512(secretKey)

58

59

println("HMAC-SHA256: ${hmacSha256.hex()}")

60

println("HMAC-SHA512: ${hmacSha512.hex()}")

61

```

62

63

### File Integrity Verification

64

65

```kotlin

66

val fs = FileSystem.SYSTEM

67

val filePath = "/tmp/important-file.txt".toPath()

68

69

// Create a file with content

70

fs.write(filePath) {

71

writeUtf8("This is important data that needs integrity verification.")

72

}

73

74

// Calculate file hash

75

val fileContent = fs.read(filePath) { readByteString() }

76

val originalHash = fileContent.sha256()

77

println("Original SHA-256: ${originalHash.hex()}")

78

79

// Simulate file modification

80

fs.write(filePath) {

81

writeUtf8("This is important data that needs integrity verification. MODIFIED!")

82

}

83

84

// Verify integrity

85

val modifiedContent = fs.read(filePath) { readByteString() }

86

val modifiedHash = modifiedContent.sha256()

87

88

if (originalHash == modifiedHash) {

89

println("✓ File integrity verified")

90

} else {

91

println("✗ File has been modified!")

92

println("Modified SHA-256: ${modifiedHash.hex()}")

93

}

94

```

95

96

## Buffer Hashing

97

98

Buffer objects provide the same hash functions as ByteString:

99

100

### Buffer Hash Methods

101

102

```kotlin { .api }

103

expect class Buffer {

104

// Hash functions (same as ByteString)

105

fun md5(): ByteString

106

fun sha1(): ByteString

107

fun sha256(): ByteString

108

fun sha512(): ByteString

109

fun hmacSha1(key: ByteString): ByteString

110

fun hmacSha256(key: ByteString): ByteString

111

fun hmacSha512(key: ByteString): ByteString

112

}

113

```

114

115

### Streaming Hash Calculation

116

117

```kotlin

118

// Calculate hash of streaming data without loading everything into memory

119

fun calculateStreamingHash(source: Source): ByteString {

120

val buffer = Buffer()

121

val hashBuffer = Buffer()

122

123

source.use { input ->

124

// Read data in chunks and accumulate for hashing

125

while (!input.exhausted()) {

126

val bytesRead = input.read(buffer, 8192L) // 8KB chunks

127

if (bytesRead > 0) {

128

// Copy to hash buffer

129

buffer.copyTo(hashBuffer)

130

buffer.clear()

131

}

132

}

133

}

134

135

return hashBuffer.sha256()

136

}

137

138

// Usage with large file

139

val largeFile = "/tmp/large-file.txt".toPath()

140

val fs = FileSystem.SYSTEM

141

142

// Create large file

143

fs.write(largeFile) {

144

repeat(10000) { i ->

145

writeUtf8("Line $i: This is a line in a large file for hash testing.\n")

146

}

147

}

148

149

// Calculate hash efficiently

150

val fileHash = calculateStreamingHash(fs.source(largeFile))

151

println("Large file SHA-256: ${fileHash.hex()}")

152

```

153

154

## HashingSource and HashingSink

155

156

For continuous hash calculation during I/O operations, Okio provides `HashingSource` and `HashingSink`.

157

158

### HashingSource

159

160

```kotlin { .api }

161

expect class HashingSource(source: Source, digest: Digest) : Source {

162

val hash: ByteString

163

164

override fun read(sink: Buffer, byteCount: Long): Long

165

override fun timeout(): Timeout

166

override fun close()

167

}

168

```

169

170

### HashingSink

171

172

```kotlin { .api }

173

expect class HashingSink(sink: Sink, digest: Digest) : Sink {

174

val hash: ByteString

175

176

override fun write(source: Buffer, byteCount: Long)

177

override fun flush()

178

override fun timeout(): Timeout

179

override fun close()

180

}

181

```

182

183

### Digest Interface

184

185

```kotlin { .api }

186

interface Digest {

187

fun update(input: ByteArray, offset: Int, byteCount: Int)

188

fun digest(): ByteArray

189

fun reset()

190

}

191

```

192

193

### Usage Examples

194

195

```kotlin

196

// Create digest instances (platform-specific implementation)

197

fun createSha256Digest(): Digest = // Platform-specific SHA-256 digest implementation

198

199

// Hash data while reading

200

fun hashWhileReading(source: Source): Pair<ByteString, ByteString> {

201

val sha256Digest = createSha256Digest()

202

val hashingSource = HashingSource(source, sha256Digest)

203

val buffer = Buffer()

204

205

hashingSource.use { hasher ->

206

buffer.writeAll(hasher)

207

}

208

209

return Pair(buffer.readByteString(), hasher.hash)

210

}

211

212

// Hash data while writing

213

fun hashWhileWriting(data: ByteString, sink: Sink): ByteString {

214

val sha256Digest = createSha256Digest()

215

val hashingSink = HashingSink(sink, sha256Digest)

216

217

hashingSink.use { hasher ->

218

hasher.write(Buffer().write(data), data.size.toLong())

219

}

220

221

return hashingSink.hash

222

}

223

224

// File copy with integrity verification

225

fun copyWithHash(sourcePath: Path, targetPath: Path): ByteString {

226

val fs = FileSystem.SYSTEM

227

val sha256Digest = createSha256Digest()

228

229

val hash = fs.sink(targetPath).use { targetSink ->

230

val hashingSink = HashingSink(targetSink, sha256Digest)

231

232

fs.source(sourcePath).use { sourceFile ->

233

hashingSink.writeAll(sourceFile)

234

}

235

236

hashingSink.hash

237

}

238

239

println("File copied with SHA-256: ${hash.hex()}")

240

return hash

241

}

242

```

243

244

## Password Hashing and Key Derivation

245

246

While Okio doesn't provide built-in password hashing functions like bcrypt or Argon2, you can use HMAC for key derivation:

247

248

### PBKDF2-style Key Derivation

249

250

```kotlin

251

// Simple PBKDF2-style key derivation using HMAC-SHA256

252

fun deriveKey(password: String, salt: ByteString, iterations: Int, keyLength: Int): ByteString {

253

var derivedKey = (password + salt.utf8()).encodeUtf8()

254

255

repeat(iterations) {

256

derivedKey = derivedKey.hmacSha256(salt)

257

}

258

259

// Truncate or extend to desired length

260

return if (derivedKey.size >= keyLength) {

261

derivedKey.substring(0, keyLength)

262

} else {

263

// For simplicity, just repeat if needed (not cryptographically ideal)

264

val buffer = Buffer()

265

while (buffer.size < keyLength) {

266

buffer.write(derivedKey)

267

}

268

buffer.readByteString(keyLength.toLong())

269

}

270

}

271

272

// Usage

273

val password = "user-password"

274

val salt = "random-salt-12345".encodeUtf8()

275

val iterations = 10000

276

val keyLength = 32 // 256 bits

277

278

val derivedKey = deriveKey(password, salt, iterations, keyLength)

279

println("Derived key: ${derivedKey.hex()}")

280

281

// Use the same inputs to verify

282

val verificationKey = deriveKey(password, salt, iterations, keyLength)

283

println("Keys match: ${derivedKey == verificationKey}")

284

```

285

286

## Data Verification and Checksums

287

288

### Message Authentication

289

290

```kotlin

291

// Create authenticated message with HMAC

292

data class AuthenticatedMessage(

293

val data: ByteString,

294

val signature: ByteString

295

) {

296

companion object {

297

fun create(message: String, secretKey: ByteString): AuthenticatedMessage {

298

val data = message.encodeUtf8()

299

val signature = data.hmacSha256(secretKey)

300

return AuthenticatedMessage(data, signature)

301

}

302

}

303

304

fun verify(secretKey: ByteString): Boolean {

305

val expectedSignature = data.hmacSha256(secretKey)

306

return expectedSignature == signature

307

}

308

309

fun getMessage(): String = data.utf8()

310

}

311

312

// Usage

313

val secretKey = "shared-secret-key".encodeUtf8()

314

val message = "This is a secure message that needs authentication."

315

316

// Create authenticated message

317

val authMessage = AuthenticatedMessage.create(message, secretKey)

318

println("Message: ${authMessage.getMessage()}")

319

println("Signature: ${authMessage.signature.hex()}")

320

321

// Verify message

322

val isValid = authMessage.verify(secretKey)

323

println("Message is valid: $isValid")

324

325

// Test with wrong key

326

val wrongKey = "wrong-secret-key".encodeUtf8()

327

val isValidWithWrongKey = authMessage.verify(wrongKey)

328

println("Message valid with wrong key: $isValidWithWrongKey")

329

```

330

331

### File Checksum Utilities

332

333

```kotlin

334

// Generate checksum file (like sha256sum)

335

fun generateChecksumFile(filePaths: List<Path>, checksumPath: Path) {

336

val fs = FileSystem.SYSTEM

337

338

fs.write(checksumPath) {

339

filePaths.forEach { filePath ->

340

if (fs.exists(filePath) && fs.metadata(filePath).isRegularFile) {

341

val content = fs.read(filePath) { readByteString() }

342

val hash = content.sha256()

343

writeUtf8("${hash.hex()} ${filePath.name}\n")

344

}

345

}

346

}

347

}

348

349

// Verify checksums

350

fun verifyChecksums(checksumPath: Path, baseDir: Path): List<Pair<String, Boolean>> {

351

val fs = FileSystem.SYSTEM

352

val results = mutableListOf<Pair<String, Boolean>>()

353

354

fs.read(checksumPath) {

355

while (!exhausted()) {

356

val line = readUtf8Line() ?: break

357

val parts = line.split(" ", limit = 2)

358

359

if (parts.size == 2) {

360

val expectedHash = parts[0]

361

val fileName = parts[1]

362

val filePath = baseDir / fileName

363

364

if (fs.exists(filePath)) {

365

val actualContent = fs.read(filePath) { readByteString() }

366

val actualHash = actualContent.sha256().hex()

367

val matches = expectedHash.equals(actualHash, ignoreCase = true)

368

results.add(fileName to matches)

369

370

println("$fileName: ${if (matches) "OK" else "FAILED"}")

371

} else {

372

results.add(fileName to false)

373

println("$fileName: NOT FOUND")

374

}

375

}

376

}

377

}

378

379

return results

380

}

381

382

// Usage

383

val testDir = "/tmp/checksum-test".toPath()

384

val fs = FileSystem.SYSTEM

385

386

fs.createDirectory(testDir)

387

388

// Create test files

389

val testFiles = listOf("file1.txt", "file2.txt", "file3.txt")

390

testFiles.forEach { fileName ->

391

fs.write(testDir / fileName) {

392

writeUtf8("Content of $fileName")

393

}

394

}

395

396

// Generate checksums

397

val checksumFile = testDir / "checksums.sha256"

398

generateChecksumFile(testFiles.map { testDir / it }, checksumFile)

399

400

// Verify checksums

401

println("Checksum verification:")

402

val results = verifyChecksums(checksumFile, testDir)

403

val allValid = results.all { it.second }

404

println("All files valid: $allValid")

405

```

406

407

## Security Best Practices

408

409

### Constant-Time Comparison

410

411

```kotlin

412

// Prevent timing attacks when comparing hashes

413

fun constantTimeEquals(a: ByteString, b: ByteString): Boolean {

414

if (a.size != b.size) return false

415

416

var result = 0

417

for (i in 0 until a.size) {

418

result = result or (a[i].toInt() xor b[i].toInt())

419

}

420

421

return result == 0

422

}

423

424

// Secure hash comparison

425

fun verifyPasswordHash(password: String, salt: ByteString, storedHash: ByteString): Boolean {

426

val inputHash = (password + salt.utf8()).encodeUtf8().sha256()

427

return constantTimeEquals(inputHash, storedHash)

428

}

429

```

430

431

### Random Salt Generation

432

433

```kotlin

434

// Generate cryptographically secure random salt

435

fun generateSalt(length: Int = 16): ByteString {

436

val random = java.security.SecureRandom()

437

val saltBytes = ByteArray(length)

438

random.nextBytes(saltBytes)

439

return saltBytes.toByteString()

440

}

441

442

// Usage in password hashing

443

fun hashPassword(password: String): Pair<ByteString, ByteString> {

444

val salt = generateSalt()

445

val hash = (password + salt.utf8()).encodeUtf8().sha256()

446

return Pair(salt, hash)

447

}

448

449

val (salt, hash) = hashPassword("user-password")

450

println("Salt: ${salt.hex()}")

451

println("Hash: ${hash.hex()}")

452

```

453

454

## Performance Considerations

455

456

### Choosing Hash Functions

457

458

```kotlin

459

// Benchmark different hash functions

460

fun benchmarkHashFunctions(data: ByteString) {

461

val hashFunctions = listOf(

462

"MD5" to { data: ByteString -> data.md5() },

463

"SHA-1" to { data: ByteString -> data.sha1() },

464

"SHA-256" to { data: ByteString -> data.sha256() },

465

"SHA-512" to { data: ByteString -> data.sha512() }

466

)

467

468

hashFunctions.forEach { (name, hashFunc) ->

469

val startTime = System.nanoTime()

470

val hash = hashFunc(data)

471

val endTime = System.nanoTime()

472

473

val durationMs = (endTime - startTime) / 1_000_000.0

474

println("$name: ${hash.hex().take(16)}... (${durationMs}ms)")

475

}

476

}

477

478

// Test with different data sizes

479

val smallData = "Small test data".encodeUtf8()

480

val largeData = "Large test data. ".repeat(10000).encodeUtf8()

481

482

println("Small data (${smallData.size} bytes):")

483

benchmarkHashFunctions(smallData)

484

485

println("\nLarge data (${largeData.size} bytes):")

486

benchmarkHashFunctions(largeData)

487

```

488

489

### Memory-Efficient Hashing

490

491

```kotlin

492

// Hash large files without loading into memory

493

fun hashLargeFile(filePath: Path): ByteString {

494

val fs = FileSystem.SYSTEM

495

val buffer = Buffer()

496

497

fs.source(filePath).buffer().use { source ->

498

while (!source.exhausted()) {

499

val chunk = source.readByteString(minOf(65536L, source.buffer.size)) // 64KB chunks

500

buffer.write(chunk)

501

}

502

}

503

504

return buffer.sha256()

505

}

506

```

507

508

## Error Handling

509

510

Hash operations are generally safe, but I/O operations during hashing can fail:

511

512

```kotlin

513

// Robust hash calculation with error handling

514

fun safeHashFile(filePath: Path): ByteString? {

515

return try {

516

val fs = FileSystem.SYSTEM

517

fs.read(filePath) { readByteString() }.sha256()

518

} catch (e: FileNotFoundException) {

519

println("File not found: $filePath")

520

null

521

} catch (e: IOException) {

522

println("I/O error reading file: ${e.message}")

523

null

524

} catch (e: Exception) {

525

println("Unexpected error: ${e.message}")

526

null

527

}

528

}

529

530

// Verify multiple files with error handling

531

fun verifyFileHashes(fileHashes: Map<Path, String>): Map<Path, String> {

532

val results = mutableMapOf<Path, String>()

533

534

fileHashes.forEach { (path, expectedHash) ->

535

val actualHash = safeHashFile(path)

536

val status = when {

537

actualHash == null -> "ERROR"

538

actualHash.hex().equals(expectedHash, ignoreCase = true) -> "OK"

539

else -> "MISMATCH"

540

}

541

results[path] = status

542

println("${path.name}: $status")

543

}

544

545

return results

546

}

547

```