1. 31
  1.  

  2. 23

    the probability that changeme is actually valid base64 encoding must be very low

    On the contrary, any 8 char alphanumeric string is valid base64. All such strings with a length divisible by 4, in fact, as those strings are guaranteed not to need trailing padding.

    1. 3

      I remember with distinct horror the first time I realize that rot13(base64(data)) is also valid base64, and that there’s probably a developer out there that was using this…

    2. 14

      Any alphanumeric string with a length that is a multiple of 4 is valid Base64 string.

      $ printf AAAA | base64 --decode | od -tx1
      0000000    00  00  00
      0000003
      
      $ printf AAAAAAAA | base64 --decode | od -tx1
      0000000    00  00  00  00  00  00
      0000006
      
      $ printf AQEB | base64 --decode | od -tx1
      0000000    01  01  01
      0000003
      
      $ printf AQID | base64 --decode | od -tx1
      0000000    01  02  03
      0000003
      
      $ printf main | base64 --decode | od -tx1
      0000000    99  a8  a7
      0000003
      
      $ printf scrabble | base64 --decode | od -tx1
      0000000    b1  ca  da  6d  b9  5e
      0000006
      
      $ printf 12345678 | base64 --decode | od -tx1
      0000000    d7  6d  f8  e7  ae  fc
      0000006
      

      Since + and / are also used as symbols in Base64 encoding (for binary 111110 and 111111, respectively), we also have:

      $ printf 1+2+3+4+5/11 | base64 --decode | od -tx1
      0000000    d7  ed  be  df  ee  3e  e7  fd  75
      0000011
      
      $ printf "\xd7\xed\xbe\xdf\xee\x3e\xe7\xfd\x75" | base64
      1+2+3+4+5/11
      
      1. 7

        Better yet: do not use dummy values. Force your users to set the values that are required.
        If you don’t, you’ll end up with default values for things such as keys and salts and put your users at risk.
        You don’t want to hardcode a ThisTokenIsNotSoSecretChangeIt in your configuration, because no one will actually change it.

        1. 5

          Create values that raise an error when it is attempted to be used:

          class ChangeMe:
              def __init__(self, name):
                  self.name = name
          
              def __str__(self):
                  raise ValueError(f"Please configure {self.name}.")
          
          # In configuration:
          SOME_VALUE = ChangeMe("SOME_VALUE")
          
          # In your library code:
          some_operation(str(SOME_VALUE))  # The error will automatically bubble.
          

          You could also use Python’s descriptor system by storing your configuration inside a Config object and having ChangeMe define __get__ to raise an error. Other languages may have other ways but Python provides these sorts of niceties.

          1. 5

            Use a language that can express optional values in the type system:

            data Config f = Config
              { foo :: f Text
              , bar :: f Int
              }
            
            type PartialConfig = Config Maybe
            type FullConfig = Config Identity
            
            completeConfig :: PartialConfig -> Maybe FullConfig
            
        2. 2

          “Not using dummy values” -comment aside, I found this article delightfully genuine and fun to read. Do a thing with your code and then lose hours or days looking for problems, when the problem is a simple typo or a forgotten // TODO: implement this :) Refreshing to see it happening to others :)

          1. 3

            Lately I always grep for TODOs before I run things.

            1. 5

              Great idea for a pre-commit hook right there

              1. 1

                If your language generates warnings when you call deprecated code, then you can make a “deprecated” function that crashes the program when called.

            2. 1

              Reminds me of how we solved a JavaScript hacking challenge in university by doing the reverse. Generating a specific string by calling base64-encode (btoa) on a binary blob.