1. 27
    1. 2

      It seems to me that the cipher functions are vulnerable to reused key such that: E(c1) ^ E(c2) == c1 ^ c2. Is that right?

      Maybe a solution to not reuse any part of of the cipher stream would be to use 52 unique nonce and try them all until a valid card is decrypted.

      eg.

      e := func(src []byte, nonce: int) []byte {...}
      d := func(src []byte) []byte {
         ...
         for nonce in range(52) {
      		dcipher, _ := chacha20.NewUnauthenticatedCipher(key[:32], nonce)
      		dcipher.XORKeyStream(res, src)
      		if isValidCard(res) { return res }
      	}
      }
      
      for i, card := range deck {
      	encryptedDeck = append(encryptedDeck, Eb(card.name(), i))
      }
      

      That said, isn’t something that asymetric crypto could solve. My crypto is rusty, but if I remember well, there were some construct where you could encrypt with different keys and decrypt in different order.

      1. 1
      2. 1

        I also just noticed this issue while I was wondering about whether the encryption function is deterministic. With deterministic encryption, wouldn’t the encrypted deck always look the same, and you could recognize encrypted cards from previous rounds?

        Your solution would also help with that, if you re-generate the nonces each round, I think.

        1. 1

          Encryption relies on the key (passphrase). With this game you should never reuse the same passphrase, both because it would lead to reused key attack, but especially because you give it away at the end of each game.

          1. 1

            Right, I misunderstood what a “game” is here. Thanks!