Solving the CIA Kryptos Code (Part 4)
Tags: cryptography, cryptanalysis, typescript
Published: 2024-01-26 22:55:00

---
title: Solving the CIA Kryptos Code (Part 4)
published: true
description: 
tags: cryptography, cryptanalysis, typescript
series: Solving the CIA Kryptos code
canonical_url: https://isaaclyman.com/blog/posts/kryptos-4/
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8uqzn666fv3ue94f2kie.jpg
published_at: 2024-01-26 22:55 +0000
---

_You can find all the code for this series [on GitHub](https://github.com/isaaclyman/kryptos-typescript)._

Kryptos 4 is, I repeat, unsolved. We're very unlikely to solve it today. Enthusiastic codebreakers have been [putting together clues](https://elonka.com/kryptos/) and proposing theories for decades. At least half of them are like this:

![Meme of Charlie Day in slacks and a tie, looking over-caffeinated and obsessive, standing next to a corkboard covered with computer printouts and red twine](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cuyslabmqpwjzrhnc120.png)

But hey, since we've got some code ready to go, we might as well try.

## Setup

All the code we've written so far is available [on GitHub](https://github.com/isaaclyman/kryptos-typescript). I won't repeat it here. If you want to play along, you can clone the repo and use [`ts-node`](https://www.npmjs.com/package/ts-node) to run the files.

## Kryptos 4

The source text of Kryptos 4 is:

```
OBKR
UOXOGHULBSOLIFBBWFLRVQQPRNGKSSO
TWTQSJQSSEKZZWATJKLUDIAWINFBNYP
VTTMZFPKWGDKZXTJCDIGKUHUAUEKCAR
```

Thanks to a few clues from the sculptor, we know the decrypted message includes the phrases "BERLINCLOCK" and "EASTNORTHEAST" at certain positions. With that knowledge, we can write a function to rate possible solutions:

```ts
export function ratePossibleK4Solution(attempt: string): number {
  attempt = attempt.toUpperCase();
  const reversed = attempt.split('').reverse().join('');

  let score = 0;

  if (attempt.includes('EASTNORTHEAST') || reversed.includes('EASTNORTHEAST')) {
    score++;
  }

  if (
    attempt.slice(21).startsWith('EASTNORTHEAST') ||
    reversed.slice(21).startsWith('EASTNORTHEAST')
  ) {
    score++;
  }

  if (attempt.includes('BERLINCLOCK') || reversed.includes('BERLINCLOCK')) {
    score++;
  }

  if (
    attempt.slice(63).startsWith('BERLINCLOCK') ||
    reversed.slice(63).startsWith('BERLINCLOCK')
  ) {
    score++;
  }

  return score;
}
```

A score of 4 would be incredible, but even 2 would be exciting. As I've written code for Kryptos 1-3, I've made several little mistakes that sometimes resulted in a decrypted but offset message, like `VEWATERILO` instead of `ILOVEWATER`. So getting the hinted phrases to turn up _at all_ could turn out to be a complete solution.

## Attempt 1

Let's try all the ciphers we've built so far:

- Vigenere with a standard alphabet
- Vigenere with the special KRYPTOS alphabet
- Keyed columnar tranposition
- Route transposition
- Routed keyed columnar transposition

```ts

const results = [];

// VIGENERE > PALIMPSEST

const vigStandard1 = vigDecrypt(encryptedK4, 'PALIMPSEST', standardAlphabet);
results.push({
  description: 'Vigenere | PALIMPSEST | Standard alphabet',
  score: ratePossibleK4Solution(vigStandard1),
  decrypted: vigStandard1,
});

const vigReversed1 = vigEncrypt(encryptedK4, 'PALIMPSEST', standardAlphabet);
results.push({
  description: 'Vigenere (reversed) | PALIMPSEST | Standard alphabet',
  score: ratePossibleK4Solution(vigReversed1),
  decrypted: vigReversed1,
});

const vigKryptos1 = vigDecrypt(
  encryptedK4,
  'PALIMPSEST',
  generateAlphabet('KRYPTOS')
);
results.push({
  description: 'Vigenere | PALIMPSEST | KRYPTOS alphabet',
  score: ratePossibleK4Solution(vigKryptos1),
  decrypted: vigKryptos1,
});

const vigReversedKryptos1 = vigEncrypt(
  encryptedK4,
  'PALIMPSEST',
  generateAlphabet('KRYPTOS')
);
results.push({
  description: 'Vigenere (reversed) | PALIMPSEST | KRYPTOS alphabet',
  score: ratePossibleK4Solution(vigReversedKryptos1),
  decrypted: vigReversedKryptos1,
});

// VIGENERE > ABSCISSA

const vigStandard2 = vigDecrypt(encryptedK4, 'ABSCISSA', standardAlphabet);
results.push({
  description: 'Vigenere | ABSCISSA | Standard alphabet',
  score: ratePossibleK4Solution(vigStandard2),
  decrypted: vigStandard2,
});

const vigReversed2 = vigEncrypt(encryptedK4, 'ABSCISSA', standardAlphabet);
results.push({
  description: 'Vigenere (reversed) | ABSCISSA | Standard alphabet',
  score: ratePossibleK4Solution(vigReversed2),
  decrypted: vigReversed2,
});

const vigKryptos2 = vigDecrypt(
  encryptedK4,
  'ABSCISSA',
  generateAlphabet('KRYPTOS')
);
results.push({
  description: 'Vigenere | ABSCISSA | KRYPTOS alphabet',
  score: ratePossibleK4Solution(vigKryptos2),
  decrypted: vigKryptos2,
});

const vigReversedKryptos2 = vigEncrypt(
  encryptedK4,
  'ABSCISSA',
  generateAlphabet('KRYPTOS')
);
results.push({
  description: 'Vigenere (reversed) | ABSCISSA | KRYPTOS alphabet',
  score: ratePossibleK4Solution(vigReversedKryptos2),
  decrypted: vigReversedKryptos2,
});

// COLUMNAR > KRYPTOS

const columnar1 = columnarDecrypt(encryptedK4, 'KRYPTOS');
results.push({
  description: 'Keyed columnar | KRYPTOS',
  score: ratePossibleK4Solution(columnar1),
  decrypted: columnar1,
});

const columnarReversed1 = columnarEncrypt(encryptedK4, 'KRYPTOS');
results.push({
  description: 'Keyed columnar (reversed) | KRYPTOS',
  score: ratePossibleK4Solution(columnarReversed1),
  decrypted: columnarReversed1,
});

// ROUTE > (86, 7)

const route1 = routeDecrypt(encryptedK4, 86, 7);
results.push({
  description: 'Route | 86,7',
  score: ratePossibleK4Solution(route1),
  decrypted: route1,
});

const routeReversed1 = routeEncrypt(encryptedK4, 86, 7);
results.push({
  description: 'Route (reversed) | 86,7',
  score: ratePossibleK4Solution(routeReversed1),
  decrypted: routeReversed1,
});

// ROUTED COLUMNAR > (86, KRYPTOS)
const routedColumnar1 = routedColumnarDecrypt(encryptedK4, 86, 'KRYPTOS');
results.push({
  description: 'Routed columnar | 86,KRYPTOS',
  score: ratePossibleK4Solution(routedColumnar1),
  decrypted: routedColumnar1,
});

const routedColumnarReversed1 = routedColumnarEncrypt(encryptedK4, 86, 'KRYPTOS');
results.push({
  description: 'Routed columnar (reversed) | 86,KRYPTOS',
  score: ratePossibleK4Solution(routedColumnarReversed1),
  decrypted: routedColumnarReversed1,
});

// LOG RESULTS

console.table(
  results.map((result) => ({
    ...result,
    decrypted: result.decrypted.slice(0, 15) + '...',
  }))
);
```

That's 14 swings and 14 misses:

```bash
┌─────────┬────────────────────────────────────────────────────────┬───────┬──────────────────────┐
│ (index) │                      description                       │ score │      decrypted       │
├─────────┼────────────────────────────────────────────────────────┼───────┼──────────────────────┤
│    0    │      'Vigenere | PALIMPSEST | Standard alphabet'       │   0   │ 'ZBZJIZFKOOFLQKC...' │
│    1    │ 'Vigenere (reversed) | PALIMPSEST | Standard alphabet' │   0   │ 'DBVZGDPSYAJLMAA...' │
│    2    │       'Vigenere | PALIMPSEST | KRYPTOS alphabet'       │   0   │ 'YRCFPYMQADMDLLG...' │
│    3    │ 'Vigenere (reversed) | PALIMPSEST | KRYPTOS alphabet'  │   0   │ 'BILJGBTJNMXXZUW...' │
│    4    │       'Vigenere | ABSCISSA | Standard alphabet'        │   0   │ 'OASPMWFOGGCJTAW...' │
│    5    │  'Vigenere (reversed) | ABSCISSA | Standard alphabet'  │   0   │ 'OCCTCGPOGIMNJKG...' │
│    6    │        'Vigenere | ABSCISSA | KRYPTOS alphabet'        │   0   │ 'XKQMSZMXSSIBNKZ...' │
│    7    │  'Vigenere (reversed) | ABSCISSA | KRYPTOS alphabet'   │   0   │ 'FJSDDETFQVRKWFE...' │
│    8    │               'Keyed columnar | KRYPTOS'               │   0   │ 'OSDRMOIBSINZLAK...' │
│    9    │         'Keyed columnar (reversed) | KRYPTOS'          │   0   │ 'OOOFRTSAINZKIUO...' │
│   10    │                     'Route | 86,7'                     │   0   │ 'OLGSIMCKFSKWFIU...' │
│   11    │               'Route (reversed) | 86,7'                │   0   │ 'OKOKOFRTSAINZKI...' │
│   12    │             'Routed columnar | 86,KRYPTOS'             │   0   │ 'RDOTPJJCLBVQXQE...' │
│   13    │       'Routed columnar (reversed) | 86,KRYPTOS'        │   0   │ 'OKOKOFRTSAINZKI...' │
└─────────┴────────────────────────────────────────────────────────┴───────┴──────────────────────┘
```

## Attempt 2

What if we try all three ciphers in sequence?

```ts
const results = [];

// K1-K3 forwards

(function () {
  const vigKryptos1 = vigDecrypt(
    encryptedK4,
    'PALIMPSEST',
    generateAlphabet('KRYPTOS')
  );
  const vigKryptos2 = vigDecrypt(
    vigKryptos1,
    'ABSCISSA',
    generateAlphabet('KRYPTOS')
  );
  const routedColumnar = routedColumnarDecrypt(vigKryptos2, 86, 'KRYPTOS');
  
  results.push({
    description: 'K1-K3 ciphers',
    score: ratePossibleK4Solution(routedColumnar),
    decrypted: routedColumnar,
  });
})();


// K3-K1 backwards

(function () {
  const routedColumnar = routedColumnarEncrypt(encryptedK4, 86, 'KRYPTOS');
  
  const vigKryptosReversed2 = vigEncrypt(
    routedColumnar,
    'ABSCISSA',
    generateAlphabet('KRYPTOS')
  );
  const vigKryptosReversed1 = vigEncrypt(
    vigKryptosReversed2,
    'PALIMPSEST',
    generateAlphabet('KRYPTOS')
  );
  
  results.push({
    description: 'K3-K1 ciphers (reversed)',
    score: ratePossibleK4Solution(vigKryptosReversed1),
    decrypted: vigKryptosReversed1,
  });
})();

// LOG RESULTS

console.table(
  results.map((result) => ({
    ...result,
    decrypted: result.decrypted.slice(0, 15) + '...',
  }))
);
```

No luck!

```bash
┌─────────┬────────────────────────────┬───────┬──────────────────────┐
│ (index) │        description         │ score │      decrypted       │
├─────────┼────────────────────────────┼───────┼──────────────────────┤
│    0    │      'K1-K3 ciphers'       │   0   │ 'HKACCRSEVYDCGBO...' │
│    1    │ 'K3-K1 ciphers (reversed)' │   0   │ 'BMKUWQJOHRJTQUH...' │
└─────────┴────────────────────────────┴───────┴──────────────────────┘
```

## Attempt 3

All right, last one. Let's try each possible combination of two ciphers from (K1, K2, K3).

```ts
const results = [];

// Vigenere PALIMPSEST + Vigenere ABSCISSA

(function () {
  const vigPalimpsest = vigDecrypt(encryptedK4, 'PALIMPSEST', generateAlphabet('KRYPTOS'));
  const vigAbscissa = vigDecrypt(vigPalimpsest, 'ABSCISSA', generateAlphabet('KRYPTOS'));

  results.push({
    description: 'K1 + K2 ciphers',
    score: ratePossibleK4Solution(vigAbscissa),
    decrypted: vigAbscissa,
  });
})();


(function () {
  const vigAbscissa = vigEncrypt(encryptedK4, 'ABSCISSA', generateAlphabet('KRYPTOS'));
  const vigPalimpsest = vigEncrypt(vigAbscissa, 'PALIMPSEST', generateAlphabet('KRYPTOS'));

  results.push({
    description: 'K2 + K1 ciphers (reversed)',
    score: ratePossibleK4Solution(vigPalimpsest),
    decrypted: vigPalimpsest,
  });
})();

// Vigenere PALIMPSEST + routed columnar

(function () {
  const vigKryptos = vigDecrypt(encryptedK4, 'PALIMPSEST', generateAlphabet('KRYPTOS'));
  const routedColumnar = routedColumnarDecrypt(vigKryptos, 86, 'KRYPTOS');

  results.push({
    description: 'K1 + K3 ciphers',
    score: ratePossibleK4Solution(routedColumnar),
    decrypted: routedColumnar,
  });
})();

(function () {
  const routedColumnar = routedColumnarEncrypt(encryptedK4, 86, 'KRYPTOS');
  const vigKryptos = vigEncrypt(routedColumnar, 'PALIMPSEST', generateAlphabet('KRYPTOS'));

  results.push({
    description: 'K3 + K1 ciphers (reversed)',
    score: ratePossibleK4Solution(vigKryptos),
    decrypted: vigKryptos,
  });
})();

// Vigenere ABSCISSA + routed columnar

(function () {
  const vigKryptos = vigDecrypt(encryptedK4, 'ABSCISSA', generateAlphabet('KRYPTOS'));
  const routedColumnar = routedColumnarDecrypt(vigKryptos, 86, 'KRYPTOS');

  results.push({
    description: 'K2 + K3 ciphers',
    score: ratePossibleK4Solution(routedColumnar),
    decrypted: routedColumnar,
  });
})();

(function () {
  const routedColumnar = routedColumnarEncrypt(encryptedK4, 86, 'KRYPTOS');
  const vigKryptos = vigEncrypt(routedColumnar, 'ABSCISSA', generateAlphabet('KRYPTOS'));

  results.push({
    description: 'K3 + K2 ciphers (reversed)',
    score: ratePossibleK4Solution(vigKryptos),
    decrypted: vigKryptos,
  });
})();

// LOG RESULTS

console.table(
  results.map((result) => ({
    ...result,
    decrypted: result.decrypted.slice(0, 15) + '...',
  }))
);
```

And as you might have predicted: no cigar.

```bash
┌─────────┬──────────────────────────────┬───────┬──────────────────────┐
│ (index) │         description          │ score │      decrypted       │
├─────────┼──────────────────────────────┼───────┼──────────────────────┤
│    0    │      'K1 + K2 ciphers'       │   0   │ 'UNPPHVFGKYFRYEA...' │
│    1    │ 'K2 + K1 ciphers (reversed)' │   0   │ 'IWWZYHDWKKTAHRP...' │
│    2    │      'K1 + K3 ciphers'       │   0   │ 'UAGXMCGLYLJLQHQ...' │
│    3    │ 'K3 + K1 ciphers (reversed)' │   0   │ 'RDQFBHDXANDUOIB...' │
│    4    │      'K2 + K3 ciphers'       │   0   │ 'QPZIQBCPENJFLHV...' │
│    5    │ 'K3 + K2 ciphers (reversed)' │   0   │ 'OECSOLDQBWGWPSV...' │
└─────────┴──────────────────────────────┴───────┴──────────────────────┘
```

Bummer! There's still an infinity of untried possibilities, though: different keys, different vigenere alphabets, different orders of operations, different ciphers altogether. Someday a lucky codebreaker will manage to brute-force it. Go ahead and clone the repo if you want to give it a try.

Best of luck!