Challenge

Note

Description

Try to decrypt the secret cheese password to prove you’re not the imposter! Connect to the program on our server: nc verbal-sleep.picoctf.net 56466

Solution

On connecting to the server we get this. For this challenge I had to use the hint as I had no idea what was this cipher.

Note

Hint

Remember that cipher we devised together Squeexy? The one that incorporates your affinity for linear equations???

A quick google search for a cipher that is based on linear equations and we land on Affine cipher.

Looking at the image below, we know that there are two params A (slope) and B(intercept) that can be tuned to cipher and decipher the text. Now our goal is to figure out these two values used in the above challenge.

For this I am going to use the encrypt a cheese option to get a cipher text for the plaintext I enter which will help me figure out A and B as I can bruteforce A and B until I get the same plaintext/ciphertext.

I tried to encrypt Parmesan but it looks like it only know a certain set of cheese or only cheese in capital case. After a few tries I could get it to work with Pecorino and I got the cipher text like below. Now it’s time to bruteforce the values of A and B. I wrote this little helper script to bruteforce the ciphertext (with help from the Internet).

function areCoprime(a, m) {
 
  const gcd = (x, y) => {
 
    while (y) {
 
      const t = y;
 
      y = x % y;
 
      x = t;
 
    }
 
    return x;
 
  };
 
  return gcd(a, m) === 1;
 
}
 
function modInverse(a, m) {
 
  for (let i = 1; i < m; i++) {
 
    if ((a * i) % m === 1) {
 
      return i;
 
    }
 
  }
 
  return 1;
 
}
 
function charToNum(char) {
 
  return char.toUpperCase().charCodeAt(0) - 65;
 
}
 
function numToChar(num) {
 
  return String.fromCharCode((num % 26) + 65);
 
}
 
function decryptAffine(ciphertext, a, b) {
 
  const m = 26;
 
  let plaintext = "";
 
  const aInverse = modInverse(a, m);
 
  for (let i = 0; i < ciphertext.length; i++) {
 
    const char = ciphertext[i];
 
    if (!/[a-zA-Z]/.test(char)) {
 
      plaintext += char;
 
      continue;
 
    }
 
    const isUpper = char === char.toUpperCase();
 
    const charNum = charToNum(char);
 
    let decrypted = (aInverse * (charNum - b + m)) % m;
 
    if (decrypted < 0) decrypted += m;
 
    const decryptedChar = numToChar(decrypted);
 
    plaintext += isUpper ? decryptedChar : decryptedChar.toLowerCase();
 
  }
 
  return plaintext;
 
}
 
function bruteforceAffine(ciphertext) {
 
  const m = 26;
 
  for (let a = 1; a < m; a++) {
 
    if (!areCoprime(a, m)) continue;
 
    for (let b = 0; b < m; b++) {
 
      const plaintext = decryptAffine(ciphertext, a, b);
 
      if (plaintext === "PECORINO") {
 
        return {
 
          a,
 
          b,
 
          plaintext,
 
        };
 
      }
 
    }
 
  }
 
  return null;
 
}
 
function findPlaintext(ciphertext) {
 
  const result = bruteforceAffine(ciphertext);
 
  return result;
 
}
 
function main() {
 
  const ciphertext = process.argv[2];
 
  console.log("Attempting to bruteforce affine cipher...");
 
  console.log(`Ciphertext: ${ciphertext}`);
 
  console.log("-".repeat(50));
 
  const result = findPlaintext(ciphertext);
 
  const { a, b, plaintext } = result;
 
  console.log(`Keys (a=${a}, b=${b})`);
 
  console.log(`   Plaintext: ${plaintext}`);
 
  console.log("-".repeat(50));
 
}
 
main();

Running the script reveals A = 25 and B = 7. Let us use https://cryptii.com/pipes/affine-cipher to decipher the actual ciphertext. On guessing the cheese with the value from above we get the flag.