From WikiChip
$encode Identifier - mIRC
< mirc‎ | identifiers
Revision as of 03:17, 21 April 2019 by Maroonbells (talk | contribs) (add'l info & examples)

The $encode identifier allows you to encode literal text, or text in %vars or &binvars. The $encode identifier uses either Uuencode or MIME or Base32 to encode. Additionally, $encode is capable of utilizing Blowfish to encrypt the target before encoding using u/m/a encoding.

Synopsis

; encoding only
$encode(text/%var/&binvar [, btuma] [, N] )

; encryption
$encode(text/%var/&binvar, celsirznpbtuma, key[, salt/iv if s|i|r used] [,N] )

Parameters

Encoding

text/%var/&binvar The target to be encoded
b Target is a &binvar
t Target is text (this is default target type)
u Target should be encoded using Uuencode (this is default encode type)
m Target should be encoded using Mime (base64) (favored; has shortest output)
a Target should be encoded using Base32
N Integer Reference index for the Nth chunk (can't use without at least 1 switch)

  • 'b' recognizes target is a binary variable instead of text beginning with '&'.
  • 'b' returns encoded output back to the same binary variable, then the $encode identifier 'returns' N where N is the number of encoded characters returned to the binary variable. i.e. $bvar(&binvar,0). Most common usage would be to $encode as an argument to noop.
  • When target is text or %variable, content is UTF-8 encoded before encoding. To prevent this, you must load text into a binary variable using 'bset -ta' then use $encode's 'b' switch to allow using that binary variable as the target. (Assuming string does not contain codepoints 256+)
  • 'm' and 'u' encode 3 bytes into 4 printable characters, with u also prepending a length byte before each chunk of 60 encoded characters. 'a' encodes 5 bytes into 8 printable characters.
  • If N is present, N=0 returns the number of chunks in the output, N >= 1 returns the Nth encoded chunk of the output or $null if N is greater than the N=0 value. N allows handling encoded output output of &binary strings too long to fit within %variables or mIRC's maximum line length.
  • Chunks are the length which encodes 45 input bytes. For 'm' and 'u', the 4:3 ratio causes the chunk to contain 60 encoding characters per huch, with the default 'u' also prepending with a character indicating the number of bytes encoded by that chunk. Because base32 encodes 5 input bytes as 8 output characters, 'a' chunks are length 72. Total output length is padded to be multiples of those groups of 4 or 8 printable characters plus the 'u' chunk length byte(s), with 'u' also padding strings that were already a length multiple of 4. $decode is currently able to decode encoded strings without the "`" or "=" padding added by $encode, but don't count on pre-v7.53 or other applications accepting them for all cases for all encoding types.

Encryption

c CBC encryption mode (c and e should not be used together)
e ECB encryption mode
l Literal key parameter used as key, instead of using hash of the key parameter. Has no effect with 'e' because 'e' always uses literal key.
s user defined 8-byte salt - Adds 16-byte header: RandomIV $+ Parameter#4. (valid only when 'c' also used)
i user defined 8-byte (IV) initialization vector (valid only when 'c' also used, does not prepend the header to the encrypted data)
r random IV (valid only when 'c' also used. i+r places the user-defined IV into the header as if random)
z zero padding ( no more than 1 of z and n and p should be used at the same time)
n one and zeros
p spaces padding

Notes:

  • 'N' parameter uses the same rules as non-encryption. It's an optional last parameter after those required by the presence of other switches ('ec' require <key>, 'c(s|i)' also require <salt|iv>)
  • 'e' ignores 's|i|r', same key always encrypts identical groups of 8 plaintext bytes into the same 8 ciphertext bytes.
  • 'c' default random salt allows same key to create different session-key each time salt differs.
  • Salt/IV parameter is chopped to 8 bytes, and is padded with $chr(0) bytes if shorter. ASCII 128-255 are not UTF-8 encoded into byte-pairs. Beginning v7.56, codepoints 256+ are invalid syntax instead of being replaced by the $chr(63) question mark.
  • Default 'c' encryption has 16-byte header beneath the encoding: The 8-byte string Salted__ followed by 8 'random' bytes.
  • any of s|i|r disables default salt, and s|i require presence of 4th 8-char parameter
  • <without s|r|i> changes header to: Salted__ $+ 8-random-bytes
  • 's' changes header to: Salted__ $+ Parameter-#4
  • 'r' changes header to: RandomIV $+ 8-random-bytes
  • 'ir' changes header to: RandomIV $+ Parameter-#4
  • 'i' Parameter-#4 user defined salt is required, but is kept a secret. (the encryption key is salted, but without a revealing the salt in a header.)

Note: 'i' is the only option that does not "blow your cover," as any of the other (s|r|ir|<none>) options above will positively identify that the message is Blowfish encrypted by placing a plain-text header ("Salted__" or "RandomIV") at the beginning of the encoded digest, along with the salt that was used. Using 'i' will be the most popular choice for this reason. You must provide a #4 parameter, but you can leave it empty for no-salt, or use a predictable changing salt like the current time, an incriminating nonce, or use it as a second 8-character secret password.

Rebuttal note: 'ci' cannot be the popular choice without a way for the decryptor to know the IV used when encrypting. Solutions can be, as described in examples: inserting 8 throwaway characters without caring which IV is used to decrypt the data then stripping them after decoding, or stripping the header but leaving the salt/iv and having the decryptor insert them before feeding to $decode. Also, 'blow your cover' can include using a salt parm left in the encoded string which never contain the 0x00 byte, or worse if they are simple alphanumeric text. You can minimize this issue by using the $randsalt alias for creating random Salt/IV parms, or using the examples below which avoid storing the "Salted__" or "RandomIV" string in the header, but leave the other (hopefully) random parm. Also spilling the beans is when the decoded string length is always a multiple of 8 bytes. Solution in example below. Also 'blowing the cover' is using a $null or repeating Salt/IV which results in identical encrypted strings until the block where the plaintext differs. The purpose of a Salt/IV is to be unique, and using $ctime results in different nicks using the same Salt/IV often, especially if using $ctime or other strings longer than 8 characters where everything except bytes 1-8 are silently ignored.

Padding of 8-byte blocks ensures encryption sees binary message length as exact multiple of 8. Padding is detected and removed by $decode after decryption:

  • default: if 'npz' not specified, PKCS#7 padding = appends 1-8 of $chr(N) where N is the number of bytes to be padded. ie: 1 = $chr(1), 2 = 2x $chr(2)'s, ..., 8x $chr(8)'s
  • 'n' Bit Padding = Appends $chr(128) character followed by 0-7 $chr(0)'s
  • 'p' Appends 1-8 $chr(32) spaces
  • 'z' Appends 1-8 $chr(0) nulls (when used with text strings, is invisibly stripped by /echo)
  • Don't use 'z' if target is a &binvar that's likely to end with null $chr(0)'s, and don't use 'p' if you expect the string to end with spaces. Unfortunately, one of the 4 forms of padding will always be required, so select one carefully.
  • 'cl' or 'e' literal keys are chopped at 56 UTF-8 encoded bytes of the key parameter.

If possible, use mIRC version v7.56+ which fixes most of the security issues with $encode. Starting with v7.56: 1. 'c' used without 'l' switch for non-literal keys no longer silently chop hashed key parameter to 56 bytes 2. 'i' and 's' switches no longer permit strings containing codepoint 256+ which were each silently replaced with codepoint 63. 3. Salts are now treated as 8-byte binary strings instead of being truncated at the first 0x00 byte, which resulted in huge numbers of unique random salts being treated as if identical. i.e. 1/256 of all random salts began with 0x00 and were all treated as identical. 4. No longer accepts invalid literal key parameter longer than 56 bytes then silently chop to 56 bytes. Remaining security issues your script needs to avoid: 1. Accepts invalid $null key parameter 2. Accepts invalid 's|i' parameters longer than 8 bytes, then silently chops them to the valid length 8 bytes.

Examples

Echo to the active screen the following encode text, using the Mime type:

//echo -a $encode(Hello there! This will be encoded using Mime.,m)
The 'N' parameter is ignored by $decode, but is used to encode individual chunks of long binary variables, potentially chopping UTF8 encoding characters across separate chunks. When 'N' is 0, returns the number of chunks in the input string, otherwise for N >= 1 it encodes the Nth group of 45 bytes in the input string. Note how this example splits the encoding, resulting in chunk 1 ending with the alt+195 byte and the chunk 2 begins with the alt+169 byte:
 
//var -s %a $str($chr(233),100)) , %b $encode(%a,m,1) , %c $encode(%a,m,2) , %d $decode(%b,m) vs $decode(%c,m)
$encode encrypts the file then applies u/m/a coding to change binary encrypted data to text.
Decoding with matching u/m/a displays the header and cipher binary hidden beneath:
 
//var %a $encode(text,csm,key,ParmSalt) | echo -a %a -> $decode(%a,m)
; Simple encrypted channel chat:
; 100% of security comes from %key because everyone knows %salt and $me and $chan
; message format: 1 flag + 8 salt + * message
; reacts only to channel messages containing only 1 word beginning with =
; sends only to channel mask, allows not-encrypted if input is $inpaste or $ctrlenter or begins with /
; min length 1 flag + 8 salt + 8-byte-block * 4/3 = 21
ON *:text:=*:#: { if (($2 != $null) || ($len($1) < 21)) return | secret_chat $1 }
ON &*:INPUT:#channel1,#channel2:{ if (($ctrlenter) || ($inpaste) || ($left($1,1))) return | secret_chat $1- | halt }
 
alias secret_chat {
  var %key Change this Shared Secret
  var %session_key $sha1($iif($event == text,$nick,$me) %key $chan)
  if ($event == text) {
    var %salt $mid($1,2,8) | var %text $decode($mid($1,10),cim,%session_key,%salt)
    echo -tcg normal # Decoded $+(<,$nick,>,:) %text
  }
  else {
    var %salt $regsubex($str(x,8),/x/g,$r(!,~)) | var %text $encode($1-,cim,%session_key,%salt)
    var %msg $+(=,%salt,%text) | msg # %msg | echo 3 -tg # Channel sees Encryption of: $1-
  }
}

To use random-salt or random-iv without including an identifying header, you can either strip the header when using 'c' or 'cs' or 'cr' switches, or include extra bytes of junk text in the message which are thrown away. This method does NOT work when encoded using a SALT.

1a. sender strips header from message to be sent:
//bset -tc &v 1 $encode(test message,mc,key) | noop $decode(&v,bm) | bcopy -c &v2 1 &v 9 -1 | noop $encode(&v2,bm) | echo -a $bvar(&v2,1-).text
result which randomly changes each time due to random salt: ymmO4O7SlZNkAY/fKOUwvZHOo1gVxz6W
 
1b. receiver takes "1a" result and adds header text before decryption:
//bset -tc &v 1 ymmO4O7SlZNkAY/fKOUwvZHOo1gVxz6W | noop $decode(&v,bm) | bset -tc &v2 1 Salted__ | bcopy &v2 9 &v 1 -1 | noop $encode(&v2,bm) | echo -a $decode($bvar(&v2,1-).text,mc,key)
result: test message
 
1c. Same can be done to remove Random IV header then re-attach it.
//bset -tc &v 1 $encode(test message,mcr,key) | noop $decode(&v,bm) | bcopy -c &v2 1 &v 9 -1 | noop $encode(&v2,bm) | echo -a $bvar(&v2,1-).text
random result: kq23174/ycXONgANFABCKMqWXkilHRiM
decrypt:
//bset -tc &v 1 kq23174/ycXONgANFABCKMqWXkilHRiM | noop $decode(&v,bm) | bset -tc &v2 1 RandomIV | bcopy &v2 9 &v 1 -1 | noop $encode(&v2,bm) | echo -a $decode($bvar(&v2,1-).text,mcr,key)
result: test message
2a. Using the wrong IV when decrypting results in the 1st 8 bytes being decrypted as junk, but the remainder of the message is valid. So this example uses the wrong IV to decrypt, then throws away the junk text:
 
//echo -a $encode(JunkTextReal Message Goes here,mci,key,$rand(10000000,99999999))
Random Output: eGERS2xKFYOoQetfnLhWLW5GNHH6XJyNTnKT5g7M9Hs=
 
2b. Now ignore the 1st 8 bytes of garbage output:
 
//echo -a $mid($decode(eGERS2xKFYOoQetfnLhWLW5GNHH6XJyNTnKT5g7M9Hs=,mci,key,$rand(10000000,99999999)),9)
 
2c. The above works ONLY when using random IV where all characters are in the ASCII range 1-127. Codepoints 128-255 in one IV which aren't matched in the other can result in the 1st 8 characters not being the same as the 1st 8 bytes. To handle that situation, you should decode with:
 
//bset -t &v 1 eGERS2xKFYOoQetfnLhWLW5GNHH6XJyNTnKT5g7M9Hs= | noop $decode(&v,bmci,key,$rand(10000000,99999999)) | echo -a $bvar(&v,9-).text
3. CBC and ECB messages always decode to a length that's a multiple of 8. To disguise this (as if there's another reason you'd be sending mimed binary strings for other reasons) is to randomly append 0-7 bytes of ignored padding. To do this, you need to use padding 'z' to ensure that padding isn't displayed as garbage. This example shows a fixed key:salt producing identical output except for random 0-7 bytes appended:
 
//bset -tc &v 1 $encode(example test message,mciz,key,saltsalt) | var %rand $rand(0,7) | if (%rand isnum 0-7) { noop $decode(&v,bm) | bset &v -1 $regsubex($str(x,%rand),/x/g,$rand(0,255) $chr(32) ) | noop $encode(&v,bm) } | var %msg $bvar(&v,1-).text | echo -a %msg -> $decode(%msg,mci,key,saltsalt)

If using pre-v7.56 random salt, or if using 'ir|s' switches to create user-defined Salt's or IV's, you SHOULD use the $randsalt alias to salt strings from the entire valid text range of codepoints 1-255. This increases the possible combinations to as high as 255^8, which is the 96.9% of valid 8-byte strings which don't contain the 0x00 byte. This makes it much less likely where the birthday paradox produces messages with identical salt/iv's.

alias randsalt returnex $regsubex($str(x,8),/x/g,$chr($rands(1,255)))
alias randsalt returnex $regsubex($str(x,8),/x/g,$chr($rand( 1,255)))
  • Note: Because this is CBC mode without authentication, the decrypted message is vulnerable to trace-less bit-flipping of the 1st 8 bytes of the message into anything the attacker wishes by simply manipulating the IV and knowing the exact contents of the 1st 8 bytes of the plaintext message. The attacker does not need to know the key, nor even need to see how the message has been encrypted. This is not a Blowfish weakness, as the same thing could happen with AES where the larger blocksize would instead expose 16 bytes. As an example, use any message where the 1st 8 bytes are all alpha characters and which do NOT contain a space character. Then encrypt with any lower-case text salt, and decrypt with the upper-case equivalent. This results in the decrypted message having the upper/lower case of each text character flipped, but the remainder of the message is NOT affected:
//var -s %iv $regsubex($str(x,8),/x/g,$rand(a,z)) , %msg $encode(BitFlipping Example,mci,key,$upper(%iv)) | echo -a $decode(%msg,mci,key,$lower(%iv))
result: bITfLIPPing Example

Solution: If worried about this, you should avoid using the 'i' and/or 'r' switches, and always use either a random or user-defined salt. This message hashes key_parameter+salt into binary_56_byte_key+derived_IV, which shields the IV from someone who doesn't know the key.

Note: If using v7.52-7.55, beware of key parameter silently chopped to 56 bytes. The following examples always produce identical output each time the command is repeated, demonstrating silent ignore of key beyond byte 56.

CBC hashed-key with salt:
//echo -a $version $encode(testtest,mcs,$str(a,55) $+ $chr($rand(192,255)) ,saltsalt)
CBC literal key with IV:
//echo -a $version $encode(testtest,mcirl,$str(a,55) $+ $chr($rand(192,255)) , (8)bytes)
ECB literal key:
//echo -a $version $encode(testtest,me,$str(a,55) $+ $chr($rand(192,255)) )
CBC hashed-key with IV:
//echo -a $version $encode(testtest,mcir,$str(a,55) $+ $chr($rand(192,255)) , (8)bytes)

Note: 'mc' or 'mcr' or 'mcrl' switch combos also silently ignored the 57th-and-later bytes, as demonstrated by being able to decode those messages without the full key:

//var -s %msg $encode(testtest,mc,$str(a,56) $+ key) | echo -a $version : $decode(%msg,mc,$str(a,56) )

Security flaws remaining with v7.56:

1. Allows encrypting where the key parameter is $null:
//echo -a $encode(message,mc,$null)
2. Silently ignores salt and IV parameter beyond 8th character, producing identical outputs:
//echo -a $encode(message,mcs,$null,12345678 $+ $rand(a,z))
//echo -a $encode(message,mcir,$null,12345678 $+ $rand(a,z))
//echo -a $encode(message,mci,$null,12345678 $+ $rand(a,z))

(Only difference between 'i' and 'ir' is that 'ir' stuffs the IV parm into header as if it's random. The extra 16 byte header lengthens the encrypted string.

  • NOTE: Your key should be long enough to make it difficult for someone to brute-force guess it. You can't count on the guesser using a mIRC script which contains overhead which slows down the guessing. Beginning v7.56, you can use a key parameter as long as possible, and the entire string will be hashed to generate the key. When using 'r' without 'l', the key parameter is hashed in a way which does not generate more than 2^128 possible keys. When using a salted key, 'c' without 'r' or 'i' switches, the hash method has some keys which can never be generated, but a long enough key parameter can generate well over 2^400 possible keys. If avoiding use of a 'salt', where you use a user-input or random IV, a simplistic literal hex string can create more than the 2^128 possible keys, using the switch combos 'mcrl' or 'mcril' or 'mcil'.

Compatibility

Added: mIRC v5.8
Added on: 05 Sep 2000
Note: Unless otherwise stated, this was the date of original functionality.
Further enhancements may have been made in later versions.

See Also