private static byte[] ScedaFunc(byte[] input, byte[] key, byte[] initializationVector, bool decrypting) { byte[] retVal = new byte[input.Length]; byte[] blockKey = new byte[49]; Array.Copy(key, 0, blockKey, 0, 16); Array.Copy(initializationVector, 0, blockKey, 16, 8); Array.Copy(initializationVector, 0, blockKey, 24, 8); Array.Copy(initializationVector, 0, blockKey, 32, 8); Array.Copy(initializationVector, 0, blockKey, 40, 8); blockKey[48] = 0; for (int i = 0; i < input.Length / 16; i++) { byte[] hashedBlockKey = SCEDA.Digest(blockKey); for (int j = 0; j < 16; j++) { retVal[i * 16 + j] = (byte)(input[i * 16 + j] ^ hashedBlockKey[j]); } if (decrypting) { Array.Copy(input, i * 16, blockKey, 16, 16); Array.Copy(retVal, i * 16, blockKey, 32, 16); } else { Array.Copy(retVal, i * 16, blockKey, 16, 16); Array.Copy(input, i * 16, blockKey, 32, 16); } blockKey[32]++; } return(retVal); }
/// <summary>Decrypts a message encrypted with SCEDA.</summary> /// <param name="input">The binary representation of the encrypted message.</param> /// <param name="key">The key used to encrypt the message (it must be 16 bytes long).</param> /// <param name="initializationVector">The initialization vector used to encrypt the message (it must be 8 bytes long).</param> /// <returns>The binary uncrypted message.</returns> /// <exception cref="ScedaException">Thrown when the length of the key or the initialization vector is wrong.</exception> /// <exception cref="ScedaDecryptingException"> /// Thrown when the message cannot be decrypted (usually because the key or the initialization vector is wrong). /// Please notice: it might not always be possible to detect when the key or the initialization vector are wrong and if they are but no exception is thrown then a wrong message is returned. /// </exception> public static byte[] Decrypt(byte[] input, byte[] key, byte[] initializationVector) { byte[] retVal = SCEDA.ScedaFunc(input, key, initializationVector, true); if (key.Length != 16 || initializationVector.Length != 8) { throw new ScedaException("The key must be 16 bytes long and the initialization vector must be 8 bytes long!"); } if ((input.Length % 16) != 0) { throw new ScedaDecryptingException("The length of a message encrypted with SCEDA must always be a multiple of 16 bytes!"); } for (int i = 0; i < input.Length / 2; i++) { byte temp = retVal[i]; retVal[i] = retVal[input.Length - i - 1]; retVal[input.Length - i - 1] = temp; } int index = 0; int length = 0; for (int i = 0; i < 7; i++) { length *= 256; length += retVal[index++]; } if (length > input.Length - 16) { throw new ScedaDecryptingException(); } Array.Copy(SCEDA.ScedaFunc(retVal.Skip(7).ToArray(), key, retVal.Skip(((length + 15) / 16) * 16 + 7).ToArray(), true), 0, retVal, 0, length); return(retVal.Take(length).ToArray()); }
/// <summary>Encrypts a message using SCEDA.</summary> /// <param name="input">The binary representation of the message to encrypt.</param> /// <param name="key">The key to be used. It must be 16 bytes long.</param> /// <param name="initializationVector">The initialization vector to be used (it must be provided and should not always be the same, but it is not required to be a secret). It must be 8 bytes long.</param> /// <returns>The binary encrypted message.</returns> public static byte[] Encrypt(byte[] input, byte[] key, byte[] initializationVector) { byte[] retVal = new byte[((input.Length + 31) / 16) * 16]; int length = ((input.Length + 15) / 16) * 16; int temp = input.Length; int index = 6; for (int i = 0; i < 7; i++) { retVal[index--] = (byte)(temp % 256); temp /= 256; } index += 8; Array.Copy(input, 0, retVal, index, input.Length); index += input.Length; for (int i = 0; i < length - input.Length + 9; i++) { retVal[index++] = (byte)SCEDA.r.Next(255); } index = 7; Array.Copy(SCEDA.ScedaFunc(retVal.Skip(index).ToArray(), key, retVal.Skip(index + length).ToArray(), false), 0, retVal, index, ((input.Length + 15) / 16) * 16); length += 16; for (int i = 0; i < length / 2; i++) { temp = retVal[i]; retVal[i] = retVal[length - i - 1]; retVal[length - i - 1] = (byte)temp; } retVal = SCEDA.ScedaFunc(retVal, key, initializationVector, false); return(retVal); }
/// <summary>Encrypts this PDU and provides its binary representation.</summary> /// <param name="key">The encryption key to be used.</param> /// <returns>The binary representation of the PDU to be sent through the network.</returns> public byte[] ToBinary(byte[] key) { if (key.Length != 16) { throw new ScedaException("A SCEDA key must be 16 bytes long."); } string type; switch (this.Type) { case SCPduType.Hello: type = "HLO"; break; case SCPduType.Welcome: type = "ACK"; break; case SCPduType.Leave: type = "LEV"; break; case SCPduType.Message: type = "MSG"; break; case SCPduType.MalformedPduNotification: type = "BAD"; break; case SCPduType.NicknameConflictNotification: type = "CNF"; break; default: type = ""; break; } byte[] iv = SCEDA.GenerateIV(); return((new byte[] { 0, 1 }).Concat(Encoding.ASCII.GetBytes(this.ChatID + '\0')).Concat(iv).Concat(SCEDA.Encrypt(Encoding.ASCII.GetBytes(type).Concat(Encoding.ASCII.GetBytes(this.Encoding.BodyName + '\0')).Concat(this.Payload).ToArray(), key, iv)).ToArray()); }
/// <summary>Applies the ScedaDigest hashing algorithm.</summary> /// <param name="input">The array to be hashed.</param> /// <returns>The result of the ScedaDigest algorithm (16 bytes long).</returns> public static byte[] Digest(byte[] input) { byte[] retVal = new byte[16]; int blockC = (input.Length + 17) / 16; byte[] buffer = new byte[blockC * 16]; Array.Copy(input, buffer, input.Length); buffer[input.Length] = (byte)((input.Length / 256) % 256); buffer[input.Length + 1] = (byte)(input.Length % 256); for (int i = input.Length + 2; i < blockC * 16; i++) { buffer[i] = 170; } for (int i = 0; i < blockC; i++) { Array.Copy(SCEDA.DigestFunc(buffer.Skip(i * 16).Take(16).ToArray()), 0, buffer, i * 16, 16); } Array.Copy(buffer, retVal, 16); byte[] temp1 = new byte[16]; byte[] temp2 = new byte[16]; for (int i = 1; i < blockC; i++) { Array.Copy(retVal, temp1, 8); Array.Copy(buffer, 16 * i, temp1, 8, 8); Array.Copy(buffer, 16 * i + 8, temp2, 0, 8); Array.Copy(retVal, 8, temp2, 8, 8); temp1 = SCEDA.DigestFunc(temp1); temp2 = SCEDA.DigestFunc(temp2); for (int j = 0; j < 16; j++) { retVal[j] = (byte)(temp1[j] ^ temp2[j]); } } return(retVal); }
/// <summary>This factory method decrypt a binary PDU and convert it into an instance of the <see cref="SCPdu"/> structure.</summary> /// <param name="pdu">The binary content of the PDU.</param> /// <param name="key">The encryption key used for encrypting the content of the PDU.</param> /// <returns>The parsed PDU.</returns> /// <exception cref="MalformedPduException">Thrown when the PDU is broken or the given encryption key is wrong.</exception> public static SCPdu FromBinary(byte[] pdu, byte[] key) { SCPdu retVal = new SCPdu(); if (key.Length != 16) { throw new ScedaException("A SCEDA key must be 16 bytes long."); } if (pdu.Length < 12) { throw new MalformedPduException("The PDU is too short."); } if (pdu[0] != 0 || pdu[1] != 1) { throw new MalformedPduException("Unknown ScedaDigest version."); } try { retVal.ChatID = Encoding.ASCII.GetString(pdu.Skip(2).TakeWhile(item => item != 0).ToArray()); int displacement = retVal.ChatID.Length + 3; byte[] iv = pdu.Skip(displacement).Take(8).ToArray(); displacement += 8; byte[] msg = SCEDA.Decrypt(pdu.Skip(displacement).ToArray(), key, iv); displacement = 0; string type = Encoding.ASCII.GetString(msg.Take(3).ToArray()); displacement += 3; switch (type) { case "HLO": retVal.Type = SCPduType.Hello; break; case "ACK": retVal.Type = SCPduType.Welcome; break; case "LEV": retVal.Type = SCPduType.Leave; break; case "MSG": retVal.Type = SCPduType.Message; break; case "BAD": retVal.Type = SCPduType.MalformedPduNotification; break; case "CNF": retVal.Type = SCPduType.NicknameConflictNotification; break; default: throw new Exception("PDU Type not known."); } string encoding = Encoding.ASCII.GetString(msg.Skip(displacement).TakeWhile(item => item != 0).ToArray()); retVal.Encoding = Encoding.GetEncoding(encoding); displacement += encoding.Length + 1; retVal.Payload = msg.Skip(displacement).ToArray(); if ((retVal.Type == SCPduType.Hello || retVal.Type == SCPduType.Welcome) && retVal.Payload.Length == 0) { throw new Exception("Hello and Welcome PDUs must have a non-empty payload."); } } catch (Exception e) { throw new MalformedPduException(e); } return(retVal); }