Updated $encode Blowfish improvements wishlist.

1. New padding method 0-7 zeroes
2. New $encode format Base85
3. Restore no-limit hash(key length) for non-literal keys
4. A new switch to recognize key and iv|salt parameters as both being hex strings
5. New $encode format Hex.
6. New switch to defend against CBC bit-flipping of IV

(1) New padding method 0-7 zeroes
(2) New $encode format Base85

Since a likely use for the encryption is for channel messages, these would allow longer encoded messages within the limited length of a PRIVMSG. Comparing the 5:4 ratio of a base85 message to the 4:3 ratio for mime/base64, a 360 byte channel message would have a mime length of 360*4/3=480, while base85 would encoded it to only 360*5/4=450. To support the N chunk parameter, it could be the 60-character encoding of 48 bytes, though as you pointed out in an older post, UTF8 means encoding bytes can be split across 2 chunks, making the chunk parameter somewhat obsolete except for some variant of Uuencode which can have a variable chunk length preceded by a variable length-byte.

Also of consideration for shorter length is that Base85 doesn't need the padding which many version of mime expect.

The main reason for the new padding method is to allow 1/8th of all messages to be 8 bytes shorter, which means their mime encoding would be 10-11 bytes shorter. By appending 0-7 bytes, this padding method would differ from 'z' padding where it does not need to append 8 0x00's to a message that's already a multiple of 8 bytes. Since the 0x00 cannot exist in a UTF8 encoded text message, there would be no risk of incorrectly stripping the 0x00 byte belonging to the original message. This new padding method would be compatible with the existing 'z' when the message is a text string which can't contain 0x00's, or even a binary string with a format that can never end with 0x00.

The Wikipedia page for Base85 lists several kinds of 85-character alphabets. Unless there are considerations where it's helpful to exclude $ %, the RFC1924 variant is supposed to be compatible with JSON, and hopefully would be regex friendly.

(3) Restore no-limit hash(key length) for non-literal keys, as described here

(4) A new switch to recognize both strings in the 'key' and 'iv|salt' parameters as hex strings. Shouldn't need to have separate switches to have only one of the pair being hex.

As the examples here show, OpenSSL and the Crypt::CBC which $encoded has compatibility with both support allowing the passphrase|Salt|IV to all be hex.

Trying to force the literal key to always be a UTF8 text string greatly limits the number of possible valid encryption keys, and using UTF8 text can easily cause portions of the key to be silently ignored due to being pushed past the 56th byte. As mentioned here, the Salt|IV are avoiding the UTF8-encoding of codepoints 128-255 into 2 bytes each, but are silently replacing all codepoints 256+ with codepoint 63 "?".

The user may be unaware that, with and without the 'l' switch, the random characters have no effect on changing the encryption because the changing byte of the key is beyond position 56, and the random salt character is always replaced by "?", resulting in this command always having identical encryption.

//echo -a $encode(message,mcil,$str(a,55) $+ $chr($rand(192,255)),$chr($rand(256,10004)))

Hex strings would enable setting the secret key to all 2^448 values, and enable setting the Salt|IV to all 2^64 values.

(5) New $encode format 'Hex' to define the encoding format for the $1 parameter. This can be used in a way unrelated to Blowfish. If it needed to support N chunks, 30 bytes as a chunk of 60 encoding characters.

(6) New switch to defend against CBC bit-flipping of IV

The switch defends the first block of the plaintext message from being bit-flipped by manipulating the IV, without altering or garbling the rest of the message. This alias shows how an attacker can manipulate the contents within an encrypted message's 1st block if it uses CBC feedback. For this to work, the attacker needs to know only:

A. The exact not-encrypted plaintext bytes among the 1st 8 bytes of the message, which they want to change into something else.
B. The value of the corresponding byte positions in the IV used as feedback against block#1
C. The 'something else' they want to change those bytes into

They do not need to know the password.
They do not need access to the encrypted portion of the message or know the ciphertext.

This has nothing to do with Blowfish itself, it would affect any cipher in CBC mode having a public IV without an authentication string. AES would've had this same issue, where its 128 bit block size exposes double the number of bytes at the beginning of the message.

The new switch would be invalid in combination with 'e' ECB mode. The ECB mode is not vulnerable to this issue, because there is no feedback.

The syntax using the 's' switch is not vulnerable to this, because the salt hash generates the secret56:IV8 using a method which shields the resulting IV from someone who doesn't know the key.

Sufficient behavior for the new switch would be to continue storing the user-defined or RandomIV the way it currently does, but prior to being used by the encryption or decryption, there would be an ECB mode encryption of the public IV before the CBC feedback begins. The original unaltered IV would continue being stored in the encrypted message as part of the RandomIV header. This would shield the IV used by block#1 from someone who did not know the key parameter. The only compatibility issue with this switch is that decrypting without also using the new switch garbles the 1st 8 bytes of the decrypted message, but the remainder decrypts fine.

When the public IV is known, and the plaintext byte is known, each byte of the new IV becomes (old-public-IV-byte XOR existing-plaintext-byte XOR new-faked-byte).

As a test vector, the following command and output are:

//echo -a $encode(20190101 this can't be backdated,mcirl,foobar,text_iv8)
result: UmFuZG9tSVZ0ZXh0X2l2ODz3ktZrCBsKkBN1bgqwfXpp6x+aAa+5tRy95xUUyfdoSD/PXrNNWgo=

Note that the (Item#1) padding method would have shorter output due to not needing to append 8 bytes padding to a text string (or a string which would never end with 0x00) whose length was already a multiple of 8.

new pad result: UmFuZG9tSVZ0ZXh0X2l2ODz3ktZrCBsKkBN1bgqwfXpp6x+aAa+5tRy95xUUyfdo

When the IV is bitflipped, the stored IV changes, but the encrypted message is not altered:

//echo -a $decode(UmFuZG9tSVZ0ZXh1Xmp1ODz3ktZrCBsKkBN1bgqwfXpp6x+aAa+5tRy95xUUyfdoSD/PXrNNWgo=,mcirl,foobar,text_iv8)

output: 20181231 this can't be backdated

When using the new switch, where the public IV is ECB encrypted before being used to begin the CBC feedback, the encryption output changes to:

UmFuZG9tSVZ0ZXh0X2l2OEzew/Uu6XVkdQKh/u6c7UnY282m9e07WXcF1qXSgtPyzc+ecxlV8X4=

Both the attempt to bit-flip the output, or decrypting without using the new switch - would both result in the date stamp in the 1st 8 bytes being pseudo-randomly garbled, but the remainder of the message decrypts ok.

Code:
alias blowfish_CBC_bitflip_demo {
  ; next line should be one of the following choices: mcirl mcir mcr mcrl
  var %switches mcr

  :enter_key
  var -s %key $input(Input any encryption key (encode uses only the 1st 56 bytes),e)
  if (%key == $null) goto enter_key

  :enter_message
  var -s %original_message $input(Enter any Original Message 16+ characters (longer the better),e)
  if ($len(%original_message) < 16) goto enter_message

  :fake_message
  var %fake_message $input(Enter 8 characters to change the beginning of the message into,e)
  noop $regsubex(junk,%fake_message,,,&fake_msg) | if ($bvar(&fake_msg,0) < 8) goto fake_message
  echo -a encode( %original_message ,%switches, %key )

  if (i isincs %switches) {
    :enter_IV
    var -s %iv $input(Enter any text string used as IV,e) | if ($len(%iv) < 8) goto enter_IV
    var    -s %old_mime $encode(%original_message,%switches,%key, %iv)
  }
  else var -s %old_mime $encode(%original_message,%switches,%key)

  bset -t &encrypted 1 %old_mime | noop $decode(&encrypted,bm)
  bset -t &guessed_original_msg 1 %original_message | bcopy -c &guessed_original_msg 1 &guessed_original_msg 1 8
  bcopy -c &old_iv 1 &encrypted 9 8
  echo 3 -a at this point the attack begins, using ONLY the following info which does NOT include knowing the key:
  echo 4 -a assumes the first 8 bytes of the not-encrypted original message guessed as: $bvar(&guessed_original_msg,1-) $qt($bvar(&guessed_original_msg,1-).text)
  echo 4 -a knows the 8 bytes of the public IV are: $bvar(&old_iv,1-)
  echo 4 -a does not know the key, but wants to change the assumed 1st 8 bytes to: $bvar(&fake_msg,1-8) $qt($bvar(&fake_msg,1-8).text)

  ; now replace IV_byte with: old_iv_byte XOR old_plaintext_byte XOR faked_plaintext_byte
  var %i 1, %new_iv | while (%i isnum 1-8) {
    var %j $xor($bvar(&fake_msg,%i),$bvar(&guessed_original_msg,%i))
    var %j $xor(%j,$bvar(&old_iv,%i)) | var %new_iv %new_iv %j | inc %i
  }

  echo -a now the attacker replaces the old iv $qt($bvar(&old_iv,1-)) with $qt(%new_iv) then lets the intended receiver decrypt the message
  bset &encrypted 9 %new_iv | echo -a noop $encode(&encrypted,bm)
  var -s %faked_mime $bvar(&encrypted,1-).text
  echo 3 -a this is the 1st usage of the KEY since the message was encrypted,
  echo 3 -a yet the attacker can change the 1st 8 bytes of the message, without knowing the key,
  echo 3 -a just by knowing the original not-encrypted bytes, without even needing to know how those 8 bytes were encrypted
  var %a %old_mime vs %faked_mime
  if (i isincs %switches) {
    echo 4 -a decrypt original: $decode(%old_mime  ,%switches,%key,%iv)
    echo 4 -a decrypt hacked::: $decode(%faked_mime,%switches,%key,%iv)
  }
  else {
    echo 4 -a decrypt original: $decode(%old_mime  ,%switches,%key)
    echo 4 -a decrypt hacked::: $decode(%faked_mime,%switches,%key)
  }
}