4月29日
Just like
code smell I recently thought of the term UI smell. Apparently the term is already sort of used but not very much.
The system I'm currently participating on has a few:
- System message "Please press button ...": If the system already knows what button should be pressed, why ask me to do it?
- System message "Would you like to save the changes?" - "Yes" - "Can't save changes because of ...": Why don't you check if you can save before you ask me?
- System message "Message sent." - "Can't send message because of ...": Why tell me you performed the action while in fact you haven't already?
- And the best: a form that can open in 9 different modes which make the form act subtly different while there are no visual clues telling the user what mode the form is in.
I really feel sorry for the future users.
A while ago I created a little application to store my passwords compressed and encrypted using XML serialization. I created it in .NET 1.1 so I could use it a my customer's site (poor me). It's compressed and encrypted XML in an XML container so I can use XmlSerializer all the way.
I handcrafted the streaming of the encryption and compression which was fun but took some time.
In .NET 2.0 this should be easier with CryptoStream and DeflateStream. The only tricky bit is how to get the compressed & encrypted data into and out of a MemoryStream properly. If you don't close the Crypto & Deflate stream they don't finalize their output properly and if you
do close them they close the underlying MemoryStream. There's a way around this but it differs between the CryptoStream and DeflateStream. The sample code below shows how to do this (and these aren't my real passwords

of course):
The Save method required some tweaking, in short it does the following:
- create a MemoryStream and a CryptoStream,
- create a DeflateStream with leaveOpen set to true in the constructor, serialize the secret information into it and close it,
- call CryptoStream.FlushFinalBlock(),
- reset the MemoryStream to the beginning,
- create the containing object (Vault) and put in the contents of the MemoryStream,
- serialize the containing object to a file.
So, the CryptoStream has an extra method FlushFinalBlock() to tell the stream to finish its work. On the other hand DeflateStream is instructed to leave the underlying stream open. If the DeflateStream is then closed it will also finish its work.
namespace Spikes
{
public class Credential
{
[XmlAttribute]
public string Site;
[XmlAttribute]
public string User;
[XmlAttribute]
public string Password;
}
public class Secrets
{
public Credential[] Credentials;
}
public class Vault
{
[XmlAttribute]
public int Hashes;
[XmlAttribute]
public byte[] IV;
[XmlText]
public byte[] Data;
}
class Program
{
static void Main(string[] args)
{
Credential credential1 = new Credential();
credential1.Site = "Info Support";
credential1.User = "frankg";
credential1.Password = "bl@h'1";
Credential credential2 = new Credential();
credential2.Site = "Live Mail";
credential2.User = "fjtdg(at)hotmail.com";
credential2.Password = "Y0!";
Secrets secrets = new Secrets();
secrets.Credentials = new Credential[] { credential1, credential2, };
RandomNumberGenerator rng = RNGCryptoServiceProvider.Create();
byte[] iv = new byte[16];
rng.GetBytes(iv);
const string path = @"C:\Users\Frank\Documents\vault.xml";
const string password = @"P@$$\/\/w0rd";
const int hashes = 16; //1048576;
Save(secrets, path, password, hashes, iv);
secrets = Open(path, password);
Console.WriteLine(secrets.Credentials.Length);
}
static void Save(Secrets secrets, string path, string password, int hashes, byte[] iv)
{
using (MemoryStream dataStream = new MemoryStream())
{
Rijndael rijndael = Rijndael.Create();
byte[] key = Key(password, hashes);
using (CryptoStream cryptoStream = new CryptoStream(dataStream, rijndael.CreateEncryptor(key, iv),
CryptoStreamMode.Write))
{
using (Stream zipStream = new DeflateStream(cryptoStream, CompressionMode.Compress, true))
{
XmlSerializer secretsSerializer = new XmlSerializer(typeof(Secrets));
secretsSerializer.Serialize(zipStream, secrets);
}
cryptoStream.FlushFinalBlock();
dataStream.Position = 0;
Vault vault = new Vault();
vault.Hashes = hashes;
vault.IV = iv;
byte[] data = new byte[dataStream.Length];
dataStream.Read(data, 0, data.Length);
vault.Data = data;
using (FileStream vaultStream = File.Open(path, FileMode.Create))
{
XmlSerializer vaultSerializer = new XmlSerializer(typeof(Vault));
vaultSerializer.Serialize(vaultStream, vault);
}
}
}
}
static Secrets Open(string path, string password)
{
using (FileStream vaultStream = File.Open(path, FileMode.Open))
{
XmlSerializer vaultSerializer = new XmlSerializer(typeof(Vault));
Vault vault = (Vault)vaultSerializer.Deserialize(vaultStream);
byte[] key = Key(password, vault.Hashes);
using (MemoryStream dataStream = new MemoryStream())
{
dataStream.Write(vault.Data, 0, vault.Data.Length);
dataStream.Flush();
dataStream.Position = 0;
Rijndael rijndael = Rijndael.Create();
using (CryptoStream cryptoStream = new CryptoStream(dataStream, rijndael.CreateDecryptor(key,
vault.IV), CryptoStreamMode.Read))
{
using (Stream zipStream = new DeflateStream(cryptoStream, CompressionMode.Decompress))
{
XmlSerializer secretsSerializer = new XmlSerializer(typeof(Secrets));
return (Secrets)secretsSerializer.Deserialize(zipStream);
}
}
}
}
}
static byte[] Key(string password, int hashes)
{
byte[] bytes = Encoding.UTF8.GetBytes(password);
HashAlgorithm hasher = new SHA256Managed();
for (int i = 0; i < hashes; i++)
{
bytes = hasher.ComputeHash(bytes);
}
return bytes;
}
}
}