Passwords & Secrets (Encryption & Observable Collections)

0
133

Passwords & Secrets is a notepad-style app that you can protect with a master password. Therefore, it’s a great app for storing a variety of passwords and other secrets that you don’t want getting into the wrong hands. The note-taking functionality is top-notch, supporting

  • Auto-save, which makes jotting down notes fast and easy
  • Quick previews of each note
  • The ability to customize each note’s background/foreground colors and text size
  • The ability to email your notes

On top of this, the data in each note is encrypted with 256-bit Advanced Encryption Standard (AES) encryption to keep prying eyes from discovering the data. This encryption is done based on the master password, so it’s important that the user never forgets their password! There is no way for the app to retrieve the data without it, as the app does not store the password for security reasons.

To make management of the master password as easy as possible, Passwords & Secrets supports specifying and showing a password hint. It also enables you to change your password (but only if you know the current password).

Why would I need to encrypt data stored in isolated storage? Isn’t my app the only thing that can access it?

Barring any bugs in the Windows Phone OS, another app should never be able to read your app’s isolated storage. And nobody should be able to remotely peer into your isolated storage. But if skilled hackers get physical access to your phone, they could certainly read the data stored on it. Encryption makes it virtually impossible for hackers to make any sense of the stored data.

Basic Cryptography

Silverlight’s System.Security.Cryptography namespace contains quite a bit of functionality for cryptographic tasks. This app wraps the necessary pieces of functionality from this namespace in order to expose an easy-to-use Crypto class. This class exposes two simple methods—Encrypt and Decrypt—that accept the decrypted/encrypted data along with a password to use as the basis for the encryption and decryption. Listing 21.1 contains the implementation.

LISTING 21.1 Crypto.cs—The Crypto Class That Exposes Simple Encrypt and Decrypt Methods

[code]

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace WindowsPhoneApp
{
public static class Crypto
{
public static string Encrypt(string data, string password)
{
if (data == null)
return null;
using (SymmetricAlgorithm algorithm = GetAlgorithm(password))
using (MemoryStream memoryStream = new MemoryStream())
using (CryptoStream cryptoStream = new CryptoStream(
memoryStream, algorithm.CreateEncryptor(), CryptoStreamMode.Write))
{
// Convert the original data to bytes then write them to the CryptoStream
byte[] buffer = Encoding.UTF8.GetBytes(data);
cryptoStream.Write(buffer, 0, buffer.Length);
cryptoStream.FlushFinalBlock();
// Convert the encrypted bytes back into a string
return Convert.ToBase64String(memoryStream.ToArray());
}
}
public static string Decrypt(string data, string password)
{
if (data == null)
return null;
using (SymmetricAlgorithm algorithm = GetAlgorithm(password))
using (MemoryStream memoryStream = new MemoryStream())
using (CryptoStream cryptoStream = new CryptoStream(
memoryStream, algorithm.CreateDecryptor(), CryptoStreamMode.Write))
{
// Convert the encrypted string to bytes then write them
// to the CryptoStream
byte[] buffer = Convert.FromBase64String(data);
cryptoStream.Write(buffer, 0, buffer.Length);
cryptoStream.FlushFinalBlock();
// Convert the original data back to a string
buffer = memoryStream.ToArray();
return Encoding.UTF8.GetString(buffer, 0, buffer.Length);
}
}
// Hash the input data with a salt, typically used for storing a password
public static string Hash(string data)
{
// Convert the data to bytes
byte[] dataBytes = Encoding.UTF8.GetBytes(data);
// Create a new array with the salt bytes followed by the data bytes
byte[] allBytes = new byte[Settings.Salt.Value.Length + dataBytes.Length];
// Copy the salt at the beginning
Settings.Salt.Value.CopyTo(allBytes, 0);
// Copy the data after the salt
dataBytes.CopyTo(allBytes, Settings.Salt.Value.Length);
// Compute the hash for the combined set of bytes
byte[] hash = new SHA256Managed().ComputeHash(allBytes);
// Convert the bytes into a string
return Convert.ToBase64String(hash);
}
public static byte[] GenerateNewSalt(int length)
{
Byte[] bytes = new Byte[length];
// Fill the array with random bytes, using a cryptographic
// random number generator (RNG)
new RNGCryptoServiceProvider().GetBytes(bytes);
return bytes;
}
static SymmetricAlgorithm GetAlgorithm(string password)
{
// Use the Advanced Encryption Standard (AES) algorithm
AesManaged algorithm = new AesManaged();
// Derive an encryption key from the password
Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password,
Settings.Salt.Value);
// Initialize, converting the two values in bits to bytes (dividing by 8)
algorithm.Key = bytes.GetBytes(algorithm.KeySize / 8);
algorithm.IV = bytes.GetBytes(algorithm.BlockSize / 8);
return algorithm;
}
}
}

[/code]

  • Both Encrypt and Decrypt call a GetAlgorithm helper method (defined at the end of the file) to get started. The returned algorithm can create an encryptor or a decryptor, which is passed to a crypto stream that is used to drive the encryption/decryption work.
  • In Encrypt, the input string is converted to bytes based on a UTF8 encoding. These bytes can then be written to the crypto stream to perform the encryption. The encrypted bytes are retrieved by using the ToArray method on the underlying memory stream used by the crypto stream. These bytes are converted back to a stream using Base64 encoding, which is a common approach for representing binary data in a string.
  • Decrypt starts with the Base64-encoded string and converts it to bytes to be written to the crypto stream. It then uses the underlying memory stream’s ToArray method to convert the decrypted UTF8 bytes back into a string.
  • The Hash function computes a SHA256 (Secure Hash Algorithm with a 256-bit digest) cryptographic hash of the input string prepended with a random “salt.” This is sometimes called a salted hash. This app calls this method in order to store a salted hash of the password rather than the password itself, for extra security. After all, if a hacker got a hold of the data in isolated storage, the encryption would be pointless if the password were stored along with it in plain text!
  • GenerateNewSalt simply produces a random byte array of the desired length. Rather than using the simple Random class used in other apps, this method uses RNGCryptoServiceProvider, a higher-quality pseudo-random number generator that is more appropriate to use in cryptographic applications. As shown in the next section, this app calls this method only once, and only the first time the app is run. It stores the randomly generated salt in isolated storage and then uses that for all future encryption, decryption, and hashing.
  • GetAlgorithm constructs the only built-in encryption algorithm, AesManaged, which is the AES symmetric algorithm. The algorithm needs to be initialized with a secret key and an initialization vector (IV), so this is handled by the Rfc2898DeriveBytes instance.
  • Rfc2898DeriveBytes is an implementation of a password-based key derivation function—PBKDF2. This uses the password and a random “salt” value, and applies a pseudorandom function based on a SHA1 hash function many times (1000 by default). All this makes the password much harder to crack.
  • The default value of AesManaged’s KeySize property is also its maximum supported value: 256. This means that the key is 256-bits long, which is why this process is called 256-bit encryption.

Salt in Cryptography

Using salt can provide a number of benefits for slowing down hackers, especially when the salt can be kept a secret. In this app, although a salt must be passed to the constructor of Rfc2898DeriveBytes, it doesn’t really add value because the salt must be stored along with the encrypted data.The same goes for the salting of the hash inside the Hash function. Although this is good practice for a server managing multiple passwords (so dictionary-based attacks must be regenerated for each user, and so users with the same password won’t have the same hash), it is done in this app mainly for show.

The LoginControl User Control

With the Crypto class in place, we can create a login control that handles all the user interaction needed for the app’s master password. The LoginControl user control used by this app is shown in Figure 21.1. It has three different modes:

  • The new user mode, in which the user must choose their master password for the first time
  • The normal login mode, in which the user must enter their previously chosen password
  • The change password mode, in which the user can change their password (after entering their existing password)
FIGURE 21.1 The three modes of the LoginControl user control in action.
FIGURE 21.1 The three modes of the LoginControl user control in action.

Listing 21.2 contains the XAML for this control.

LISTING 21.2 LoginControl.xaml—The User Interface for the LoginControl User Control

[code]

<UserControl x:Class=”WindowsPhoneApp.LoginControl”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:local=”clr-namespace:WindowsPhoneApp”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”>
<Grid Background=”{StaticResource PhoneBackgroundBrush}”>
<!– A dim accent-colored padlock image –>
<Rectangle Fill=”{StaticResource PhoneAccentBrush}” Width=”300” Height=”364”
VerticalAlignment=”Bottom” HorizontalAlignment=”Right”
Margin=”{StaticResource PhoneMargin}” Opacity=”.5”>
<Rectangle.OpacityMask>
<ImageBrush ImageSource=”Images/lock.png”/>
</Rectangle.OpacityMask>
</Rectangle>
<ScrollViewer>
<Grid>
<!– This panel is used for both New User and Change Password modes –>
<StackPanel x:Name=”ChangePasswordPanel” Visibility=”Collapsed”
Margin=”{StaticResource PhoneMargin}”>
<!– Welcome! –>
<TextBlock x:Name=”WelcomeTextBlock” Visibility=”Collapsed”
Margin=”{StaticResource PhoneHorizontalMargin}” TextWrapping=”Wrap”>
<Run FontWeight=”Bold”>Welcome!</Run>
<LineBreak/>
Choose a password that you’ll remember. There is no way to recover …
</TextBlock>
<!– Old password –>
<TextBlock Text=”Old password” x:Name=”OldPasswordLabel”
Style=”{StaticResource LabelStyle}”/>
<PasswordBox x:Name=”OldPasswordBox” KeyUp=”PasswordBox_KeyUp”/>
<!– New password –>
<TextBlock Text=”New password” Style=”{StaticResource LabelStyle}”/>
<PasswordBox x:Name=”NewPasswordBox” KeyUp=”PasswordBox_KeyUp”/>
<!– Confirm new password –>
<TextBlock Text=”Type new password again”
Style=”{StaticResource LabelStyle}”/>
<PasswordBox x:Name=”ConfirmNewPasswordBox” KeyUp=”PasswordBox_KeyUp”/>
<!– Password hint –>
<TextBlock Text=”Password hint (optional)”
Style=”{StaticResource LabelStyle}”/>
<TextBox x:Name=”PasswordHintTextBox” InputScope=”Text”
KeyUp=”PasswordBox_KeyUp”/>
<Button Content=”ok” Click=”OkButton_Click” MinWidth=”226”
HorizontalAlignment=”Left” Margin=”0,12,0,0”
local:Tilt.IsEnabled=”True”/>
</StackPanel>
<!– This panel is used only for the Normal Login mode –>
<StackPanel x:Name=”NormalLoginPanel” Visibility=”Collapsed”
Margin=”{StaticResource PhoneMargin}”>
<TextBlock Text=”Enter your password”
Style=”{StaticResource LabelStyle}”/>
<PasswordBox x:Name=”NormalLoginPasswordBox” KeyUp=”PasswordBox_KeyUp”/>
<Button Content=”ok” Click=”OkButton_Click” MinWidth=”226”
HorizontalAlignment=”Left” local:Tilt.IsEnabled=”True”/>
</StackPanel>
</Grid>
</ScrollViewer>
</Grid>
</UserControl>

[/code]

  • This control uses a password box wherever a password should be entered. A password box is just like a text box, except that it displays each character as a circle (after a brief moment in which you see the letter you just typed). This matches the behavior of password entry in all the built-in apps. Instead of a Text property, it has a Password property.

Listing 21.3 contains the code-behind.

LISTING 21.3 LoginControl.xaml.cs—The Code-Behind for the LoginControl User Control

[code]

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WindowsPhoneApp
{
public partial class LoginControl : UserControl
{
// A custom event
public event EventHandler Closed;
public LoginControl()
{
InitializeComponent();
// Update the UI depending on which of the three modes we’re in
if (Settings.HashedPassword.Value == null)
{
// The “new user” mode
this.WelcomeTextBlock.Visibility = Visibility.Visible;
this.OldPasswordLabel.Visibility = Visibility.Collapsed;
this.OldPasswordBox.Visibility = Visibility.Collapsed;
this.ChangePasswordPanel.Visibility = Visibility.Visible;
}
else if (CurrentContext.IsLoggedIn)
{
// The “change password” mode
this.ChangePasswordPanel.Visibility = Visibility.Visible;
}
else
{
// The “normal login” mode
this.NormalLoginPanel.Visibility = Visibility.Visible;
}
}
void OkButton_Click(object sender, RoutedEventArgs e)
{
string currentHashedPassword = Settings.HashedPassword.Value;
if (currentHashedPassword != null && !CurrentContext.IsLoggedIn)
{
// We’re in “normal login” mode
// If the hash of the attempted password matches the stored hash,
// then we know the user entered the correct password.
if (Crypto.Hash(this.NormalLoginPasswordBox.Password)
!= currentHashedPassword)
{
MessageBox.Show(“”, “Incorrect password”, MessageBoxButton.OK);
return;
}
// Keep the unencrypted password in-memory,
// only until this app is deactivated/closed
CurrentContext.Password = this.NormalLoginPasswordBox.Password;
}
else
{
// We’re in “new user” or “change password” mode
// For “change password,” be sure that the old password is correct
if (CurrentContext.IsLoggedIn && Crypto.Hash(this.OldPasswordBox.Password)
!= currentHashedPassword)
{
MessageBox.Show(“”, “Incorrect old password”, MessageBoxButton.OK);
return;
}
// Now validate the new password
if (this.NewPasswordBox.Password != this.ConfirmNewPasswordBox.Password)
{
MessageBox.Show(“The two passwords don’t match. Please try again.”,
“Oops!”, MessageBoxButton.OK);
return;
}
string newPassword = this.NewPasswordBox.Password;
if (newPassword == null || newPassword.Length == 0)
{
MessageBox.Show(“The password cannot be empty. Please try again.”,
“Nice try!”, MessageBoxButton.OK);
return;
}
// Store a hash of the password so we can check for the correct
// password in future logins without storing the actual password
Settings.HashedPassword.Value = Crypto.Hash(newPassword);
// Store the password hint as plain text
Settings.PasswordHint.Value = this.PasswordHintTextBox.Text;
// Keep the unencrypted password in-memory,
// only until this app is deactivated/closed
CurrentContext.Password = newPassword;
// If there already was a password, we must decrypt all data with the old
// password (then re-encrypt it with the new password) while we still
// know the old password! Otherwise the data will be unreadable!
if (currentHashedPassword != null)
{
// Each item in the NotesList setting has an EncryptedContent property
// that must be processed
for (int i = 0; i < Settings.NotesList.Value.Count; i++)
{
// Encrypt with the new password the data that is decrypted
// with the old password
Settings.NotesList.Value[i].EncryptedContent =
Crypto.Encrypt(
Crypto.Decrypt(Settings.NotesList.Value[i].EncryptedContent,
this.OldPasswordBox.Password),
newPassword
);
}
}
}
CurrentContext.IsLoggedIn = true;
Close();
}
void PasswordBox_KeyUp(object sender, KeyEventArgs e)
{
// Allow the Enter key to cycle between text boxes and to press the ok
// button when on the last text box
if (e.Key == Key.Enter)
{
if (sender == this.PasswordHintTextBox ||
sender == this.NormalLoginPasswordBox)
OkButton_Click(sender, e);
else if (sender == this.OldPasswordBox)
this.NewPasswordBox.Focus();
else if (sender == this.NewPasswordBox)
this.ConfirmNewPasswordBox.Focus();
else if (sender == this.ConfirmNewPasswordBox)
this.PasswordHintTextBox.Focus();
}
}
public void Close()
{
if (this.Visibility == Visibility.Collapsed)
return; // Already closed
// Clear all
this.OldPasswordBox.Password = “”;
this.NewPasswordBox.Password = “”;
this.ConfirmNewPasswordBox.Password = “”;
this.NormalLoginPasswordBox.Password = “”;
The LoginControl User Control 503
this.PasswordHintTextBox.Text = “”;
// Close by becoming invisible
this.Visibility = Visibility.Collapsed;
// Raise the event
if (this.Closed != null)
this.Closed(this, EventArgs.Empty);
}
}
}

[/code]

  • This listing makes use of some of the following settings defined in a separate Settings.cs file:

    [code]

    public static class Settings
    {
    // Password-related settings
    public static readonly Setting<byte[]> Salt =
    new Setting<byte[]>(“Salt”, Crypto.GenerateNewSalt(16));
    public static readonly Setting<string> HashedPassword =
    new Setting<string>(“HashedPassword”, null);
    public static readonly Setting<string> PasswordHint =
    new Setting<string>(“PasswordHint”, null);
    // The user’s data
    public static readonly Setting<ObservableCollection<Note>> NotesList =
    new Setting<ObservableCollection<Note>>(“NotesList”,
    new ObservableCollection<Note>());
    // User settings
    public static readonly Setting<bool> MakeDefault =
    new Setting<bool>(“MakeDefault”, false);
    public static readonly Setting<Color> ScreenColor =
    new Setting<Color>(“ScreenColor”, Color.FromArgb(0xFF, 0xFE, 0xCF, 0x58));
    public static readonly Setting<Color> TextColor =
    new Setting<Color>(“TextColor”, Colors.Black);
    public static readonly Setting<int> TextSize = new Setting<int>(“TextSize”,
    22);
    // Temporary state
    public static readonly Setting<int> CurrentNoteIndex =
    new Setting<int>(“CurrentNoteIndex”, -1);
    public static readonly Setting<Color?> TempScreenColor =
    new Setting<Color?>(“TempScreenColor”, null);
    public static readonly Setting<Color?> TempTextColor =
    new Setting<Color?>(“TempTextColor”, null);
    }

    [/code]

    The salt required by Rfc2898DeriveBytes used by the Crypto class must be at least 8 bytes. With the call to GenerateNewSalt, this app generates a 16-byte salt.

  • In the normal login mode, the control must determine whether the entered password is correct. But the app doesn’t store the user’s password. Instead, it stores a salted hash of the password. Therefore, to validate the entered password, it calls the same Crypto.Hash function and checks if it matches the stored hashed value.
  • Although the unencrypted password is not persisted, it is kept in memory while the app runs so it can decrypt the user’s saved content and encrypt any new content. This is done with the CurrentContext class, defined as follows in CurrentContext.cs:

    [code]

    public static class CurrentContext
    {
    public static bool IsLoggedIn = false;
    public static string Password = null;
    }

    [/code]

  • In the change password mode, something very important must be done before the old password is forgotten. Everything that has been encrypted with the old password must be decrypted then re-encrypted with the new password. Otherwise, the data would become unreadable because the new password cannot be used to decrypt data that was encrypted with the old password!
  • Inside Close, the Password property of each password box is set to an empty string instead of null because the Password property throws an exception if set to null.
  • You can see that LoginControl is not a general-purpose control but rather tailored to this app. (Although it wouldn’t be hard to generalize it by providing a hook for the consumer to perform the data re-encryption during the password-change process.) It is used in three separate places.

The Change Password Page

The change password page, seen previously in Figure 21.1, is nothing more than a page hosting a LoginControl instance. The user can only reach this page when already signed in, so the control is automatically initialized to the “change password” mode thanks to the code in Listing 21.3. Listings 21.4 and 21.5 contain the simple XAML and codebehind for the change password page.

LISTING 21.4 ChangePasswordPage.xaml—The User Interface for Password & Secrets’ Change Password Page

[code]
<phone:PhoneApplicationPage
x:Class=”WindowsPhoneApp.ChangePasswordPage”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:phone=”clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone”
xmlns:shell=”clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone”
xmlns:local=”clr-namespace:WindowsPhoneApp”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”
SupportedOrientations=”PortraitOrLandscape” shell:SystemTray.IsVisible=”True”>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”*”/>
</Grid.RowDefinitions>
<!– The standard header –>
<StackPanel Style=”{StaticResource PhoneTitlePanelStyle}”>
<TextBlock Text=”PASSWORDS &amp; SECRETS”
Style=”{StaticResource PhoneTextTitle0Style}”/>
<TextBlock Text=”change password”
Style=”{StaticResource PhoneTextTitle1Style}”/>
</StackPanel>
<!– The user control –>
<local:LoginControl Grid.Row=”1” Closed=”LoginControl_Closed”/>
</Grid>
</phone:PhoneApplicationPage>

[/code]

LISTING 21.5 ChangePasswordPage.xaml.cs—The Code-Behind for Password & Secrets’ Change Password Page

[code]

using Microsoft.Phone.Controls;
namespace WindowsPhoneApp
{
public partial class ChangePasswordPage : PhoneApplicationPage
{
public ChangePasswordPage()
{
InitializeComponent();
}
void LoginControl_Closed(object sender, System.EventArgs e)
{
if (this.NavigationService.CanGoBack)
this.NavigationService.GoBack();
}
}
}

[/code]

The Main Page

This app’s main page contains the list of user’s notes, as demonstrated in Figure 21.2. Each one can be tapped to view and/or edit it. A button on the application bar enables adding new notes. But before the list is populated and any of this is shown, the user must enter the correct password. When the user isn’t logged in, the LoginControl covers the entire page except its header, and the application bar doesn’t have the add-note button.

FIGURE 21.2 A list of notes on the main page, in various colors and sizes.
FIGURE 21.2 A list of notes on the main page, in various colors and sizes.

The User Interface

Listing 21.6 contains the XAML for the main page.

 

LISTING 21.6 MainPage.xaml—The User Interface for Password & Secrets’Main Page

[code]

<phone:PhoneApplicationPage
x:Class=”WindowsPhoneApp.MainPage”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:phone=”clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone”
xmlns:shell=”clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone”
xmlns:local=”clr-namespace:WindowsPhoneApp”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”
SupportedOrientations=”PortraitOrLandscape” shell:SystemTray.IsVisible=”True”>
<phone:PhoneApplicationPage.Resources>
<local:DateConverter x:Key=”DateConverter”/>
</phone:PhoneApplicationPage.Resources>
<!– The application bar, with 3 menu items –>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar>
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text=”show password hint”
Click=”PasswordMenuItem_Click”/>
<shell:ApplicationBarMenuItem Text=”instructions”
Click=”InstructionsMenuItem_Click”/>
<shell:ApplicationBarMenuItem Text=”about” Click=”AboutMenuItem_Click”/>
<shell:ApplicationBarMenuItem Text=”more apps”
Click=”MoreAppsMenuItem_Click”/>
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
<Grid Background=”Transparent”>
<Grid.RowDefinitions>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”*”/>
</Grid.RowDefinitions>
<!– The standard header –>
<StackPanel Grid.Row=”0”
Style=”{StaticResource PhoneTitlePanelStyle}”>
<TextBlock Text=”PASSWORDS &amp; SECRETS”
Style=”{StaticResource PhoneTextTitle0Style}”/>
</StackPanel>
<!– Show this when there are no notes –>
<TextBlock Name=”NoItemsTextBlock” Grid.Row=”1” Text=”No notes”
Visibility=”Collapsed” Margin=”22,17,0,0”
Style=”{StaticResource PhoneTextGroupHeaderStyle}”/>
<!– The list box containing notes –>
<ListBox x:Name=”ListBox” Grid.Row=”1” ItemsSource=”{Binding}”
SelectionChanged=”ListBox_SelectionChanged”>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<!– The title, in a style matching the note –>
<Border Background=”{Binding ScreenBrush}” Margin=”24,0” Width=”800”
MinHeight=”60” local:Tilt.IsEnabled=”True”>
<TextBlock Text=”{Binding Title}” FontSize=”{Binding TextSize}”
Foreground=”{Binding TextBrush}” Margin=”12”
VerticalAlignment=”Center”/>
</Border>
<!– The modified date –>
<TextBlock Foreground=”{StaticResource PhoneSubtleBrush}”
Text=”{Binding Modified, Converter={StaticResource DateConverter}}”
Margin=”24,0,0,12”/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!– The user control –>
<local:LoginControl x:Name=”LoginControl” Grid.Row=”1”
Closed=”LoginControl_Closed”/>
</Grid>
</phone:PhoneApplicationPage>

[/code]

  • The ampersand in the app’s title is XML encoded to avoid a XAML parsing error.
  • The LoginControl user control is used as a part of this page, rather than as a separate login page, to ensure a sensible navigation flow. When the user opens the app, logs in, and then sees the data on the main page, pressing the hardware Back button should exit the app, not go back to a login page!
  • LoginControl doesn’t protect the data simply by visually covering it up; you’ll see in the code-behind that it isn’t populated until after login. And there’s no way for the app to show the data before login because the correct password is needed to properly decrypt the stored notes.
  • The list box’s item template binds to several properties of each note. (The Note class used to represent each one is shown later in this chapter.) The binding to the Modified property uses something called a value converter to change the resultant display. Value converters are discussed next.

Value Converters

In data binding, value converters can morph a source value into a completely different target value. They enable you to plug in custom logic without giving up the benefits of data binding. Value converters are often used to reconcile a source and target that are different data types. For example, you could change the background or foreground color of an element based on the value of some nonbrush data source, à la conditional formatting in Microsoft Excel. As another example, the toggle switch in the Silverlight for Windows Phone Toolkit leverages a value converter called OnOffConverter that converts the nullable Boolean IsChecked value to an “On” or “Off” string used as its default content.

In Passwords & Secrets, we want to slightly customize the display of each note’s Modified property. Modified is of type DateTimeOffset, so without a value converter applied, it would appear as follows:

[code]12/11/2012 10:18:49 PM -08:00[/code]

The -08:00 represents the time zone. It is expressed as an offset from Coordinated Universal Time (UTC).

Our custom value converter strips off the time zone information and the seconds, as that’s more information than we need. It produces a result like the following:

[code]12/11/2010 10:18 PM[/code]

Even if Modified were a DateTime instead of a DateTimeOffset, the value converter would still be useful for stripping the seconds value out of the string.

What’s the difference between the DateTime data type and DateTimeOffset?

Whereas DateTime refers to a logical point in time that is independent of any time zone, DateTimeOffset is a real point in time with an offset relative to the UTC time zone. In this app, DateTimeOffset is appropriate to use for the modified time of each note because users shouldn’t expect that point in time to change even if they later travel to a different time zone.The preceding chapter’s Alarm Clock, however, appropriately uses DateTime for the alarm time. Imagine that you set the alarm while in one time zone but you’re in a different time zone when it’s time for it to go off. If you had set your alarm for 8:00 AM, you probably expect it to go off at 8:00 AM no matter what time zone you happen to be in at the time. For most scenarios, using DateTimeOffset is preferable to DateTime.However, it was introduced into the .NET Framework years after DateTime, so the better name was already taken. (Designers of the class rejected calling it DateTime2 or DateTimeEx). Fortunately, consumers of these data types can pretty much use them interchangeably.

To create a value converter, you must write a class that implements an IValueConverter interface in the System.Windows.Data namespace. This interface has two simple methods—Convert, which is passed the source instance that must be converted to the target instance, and ConvertBack, which does the opposite. Listing 21.7 contains the implementation of the DateConverter value converter used in Listing 21.6.

LISTING 21.7 DateConverter.cs—A Value Converter That Customizes the Display of a DateTimeOffset

[code]

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace WindowsPhoneApp
{
public class DateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
DateTimeOffset date = (DateTimeOffset)value;
// Return a custom format
return date.LocalDateTime.ToShortDateString() + “ “
+ date.LocalDateTime.ToShortTimeString();
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
}

[/code]

The Convert method is called every time the source value changes. It’s given the DateTimeOffset value and returns a string with the date and time in a short format. The ConvertBack method is not needed, as it is only invoked in two-way data binding. Therefore, it returns a dummy value.

Value converters can be applied to any data binding with its Converter parameter. This was done in Listing 21.6 as follows:

[code]

<!– The modified date –>
<TextBlock Foreground=”{StaticResource PhoneSubtleBrush}”
Text=”{Binding Modified, Converter={StaticResource DateConverter}}”
Margin=”24,0,0,12”/>

[/code]

Setting this via StaticResource syntax requires an instance of the converter class to be defined in an appropriate resource dictionary. Listing 21.6 added an instance with the DateConverter key to the page’s resource dictionary:

[code]

<phone:PhoneApplicationPage.Resources>
<local:DateConverter x:Key=”DateConverter”/>
</phone:PhoneApplicationPage.Resources>

[/code]

Additional Data for Value Converters

The methods of IValueConverter are passed a parameter and a culture. By default, parameter is set to null and culture is set to the value of the target element’s Language property. However, the consumer of bindings can control these two values via Binding.ConverterParameter and Binding.ConverterCulture. For example:

[code]

<!– The modified date –>
<TextBlock Foreground=”{StaticResource PhoneSubtleBrush}”
Text=”{Binding Modified, Converter={StaticResource DateConverter},
ConverterParameter=custom data, ConverterCulture=en-US}”
Margin=”24,0,0,12”/>

[/code]

The ConverterParameter can be any custom data for the converter class to act upon, much like the Tag property on elements. ConverterCulture can be set to an Internet Engineering Task Force (IETF) language tag (such as en-US or ko-KR), and the converter receives the appropriate CultureInfo object. In DateConverter, the ToString methods already respect the current culture, so there’s no need to do anything custom with the culture.

Value converters are the key to plugging any kind of custom logic into the data-binding process that goes beyond basic formatting.Whether you want to apply some sort of transformation to the source value before displaying it or change how the target gets updated based on the value of the source, you can easily accomplish this with a class that implements IValueConverter. A very common value converter that people create is a Boolean-to-Visibility converter (usually called BooleanToVisibilityConverter) that can convert between the Visibility enumeration and a Boolean or nullable Boolean. In one direction, true is mapped to Visible, whereas false and null are mapped to Collapsed. In the other direction, Visible is mapped to true, whereas Collapsed is mapped to false.This is useful for toggling the visibility of elements based on the state of an otherwise unrelated element, all in XAML. For example, the following snippet of XAML implements a Show Button check box without requiring any procedural code (other than the value converter):

[code]

<phone:PhoneApplicationPage.Resources>
<local:BooleanToVisibilityConverter x:Key=”BooltoVis”/>
</phone:PhoneApplicationPage.Resources>

<CheckBox x:Name=”CheckBox”>Show Button</CheckBox>

<Button Visibility=”{Binding IsChecked, ElementName=CheckBox,
Converter={StaticResource BoolToVis}}”…/>

[/code]

In this case, the button is visible when (and only when) the check box’s IsChecked property is true.

The Code-Behind

The code-behind for the main page is shown in Listing 21.8.

LISTING 21.8 MainPage.xaml.cs—The Code-Behind for Password & Secrets’Main Page

[code]

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
namespace WindowsPhoneApp
{
public partial class MainPage : PhoneApplicationPage
{
IApplicationBarMenuItem passwordMenuItem;
public MainPage()
{
InitializeComponent();
this.passwordMenuItem = this.ApplicationBar.MenuItems[0]
as IApplicationBarMenuItem;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// The password menu item is “show password hint” when not logged in,
// or “change password” when logged in
if (CurrentContext.IsLoggedIn)
{
this.passwordMenuItem.Text = “change password”;
// This is only needed when reactivating app and navigating back to this
// page from the details page, because going back can instantiate
// this page in a logged-in state
this.LoginControl.Close();
}
else
{
this.passwordMenuItem.Text = “show password hint”;
}
// Clear the selection so selecting the same item twice in a row will
// still raise the SelectionChanged event
Settings.CurrentNoteIndex.Value = -1;
this.ListBox.SelectedIndex = -1;
if (Settings.NotesList.Value.Count == 0)
this.NoItemsTextBlock.Visibility = Visibility.Visible;
else
this.NoItemsTextBlock.Visibility = Visibility.Collapsed;
}
void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ListBox.SelectedIndex >= 0)
{
// Navigate to the details page for the selected item
Settings.CurrentNoteIndex.Value = ListBox.SelectedIndex;
this.NavigationService.Navigate(new Uri(“/DetailsPage.xaml”,
UriKind.Relative));
}
}
void LoginControl_Closed(object sender, EventArgs e)
{
// Now that we’re logged-in, add the “new” button to the application bar
ApplicationBarIconButton newButton = new ApplicationBarIconButton
{
Text = “new”,
IconUri = new Uri(“/Shared/Images/appbar.add.png”, UriKind.Relative)
};
newButton.Click += NewButton_Click;
this.ApplicationBar.Buttons.Add(newButton);
// The password menu item is “show password hint” when not logged in,
// or “change password” when logged in
this.passwordMenuItem.Text = “change password”;
// Now bind the notes list as the data source for the list box,
// because its contents can be decrypted
this.DataContext = Settings.NotesList.Value;
}
// Application bar handlers
void NewButton_Click(object sender, EventArgs e)
{
// Create a new note and add it to the top of the list
Note note = new Note();
note.Modified = DateTimeOffset.Now;
note.ScreenColor = Settings.ScreenColor.Value;
note.TextColor = Settings.TextColor.Value;
note.TextSize = Settings.TextSize.Value;
Settings.NotesList.Value.Insert(0, note);
// “Select” the new note
Settings.CurrentNoteIndex.Value = 0;
// Navigate to the details page for the newly created note
this.NavigationService.Navigate(new Uri(“/DetailsPage.xaml”,
UriKind.Relative));
}
void PasswordMenuItem_Click(object sender, EventArgs e)
{
if (CurrentContext.IsLoggedIn)
{
// Change password
this.NavigationService.Navigate(new Uri(“/ChangePasswordPage.xaml”,
UriKind.Relative));
}
else
{
// Show password hint
if (Settings.PasswordHint.Value == null ||
Settings.PasswordHint.Value.Trim().Length == 0)
{
MessageBox.Show(“Sorry, but there is no hint!”, “Password hint”,
MessageBoxButton.OK);
}
else
{
MessageBox.Show(Settings.PasswordHint.Value, “Password hint”,
MessageBoxButton.OK);
}
}
}
void InstructionsMenuItem_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(“/InstructionsPage.xaml”,
UriKind.Relative));
}
void AboutMenuItem_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(
new Uri(“/Shared/About/AboutPage.xaml?appName=Passwords %26 Secrets”,
UriKind.Relative));
}
}
}

[/code]

  • The first menu item on the application bar, shown expanded in Figure 21.3, reveals the password hint when the user is logged out and navigates to the change password page when the user is logged in.
FIGURE 21.3 The expanded application bar menu shows “change password” when the user is logged in.
FIGURE 21.3 The expanded application bar menu shows “change password” when the user is logged in.
  • As seen earlier, the NotesList collection used as the data context for the list box is not just any collection (like List<Note>); it’s an observable collection:

    [code]
    public static readonly Setting<ObservableCollection<Note>> NotesList =
    new Setting<ObservableCollection<Note>>(“NotesList”,
    new ObservableCollection<Note>());
    [/code]

    Observable collections raise a CollectionChanged event whenever any changes
    occur, such as items being added or removed. Data binding automatically leverages
    this event to keep the target (the list box, in this page) up-to-date at all times.
    Thanks to this, Listing 21.8 simply sets the page’s data context to the list and the
    rest takes care of itself.

The INotifyPropertyChanged Interface

Although the observable collection takes care off additions and deletions being reflected in the list box, each Note item must provide notifications to ensure that item-specific property changes are reflected in the databound list box. Note does this by implementing INotifyPropertyChanged, as shown in Listing 21.9.

LISTING 21.9 Note.cs—The Note Class Representing Each Item in the List

[code]

using System;
using System.ComponentModel;
using System.Windows.Media;
namespace WindowsPhoneApp
{
public class Note : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// A helper method used by the properties
void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
string encryptedContent;
public string EncryptedContent
{
get { return this.encryptedContent; }
set { this.encryptedContent = value;
OnPropertyChanged(“EncryptedContent”); OnPropertyChanged(“Title”); }
}
DateTimeOffset modified;
public DateTimeOffset Modified
{
get { return this.modified; }
set { this.modified = value; OnPropertyChanged(“Modified”); }
}
int textSize;
public int TextSize
{
get { return this.textSize; }
set { this.textSize = value; OnPropertyChanged(“TextSize”); }
}
Color screenColor;
public Color ScreenColor
{
get { return this.screenColor; }
set { this.screenColor = value;
OnPropertyChanged(“ScreenColor”); OnPropertyChanged(“ScreenBrush”); }
}
Color textColor;
public Color TextColor
{
get { return this.textColor; }
set { this.textColor = value;
OnPropertyChanged(“TextColor”); OnPropertyChanged(“TextBrush”); }
}
// Three readonly properties whose value is computed from other properties:
public Brush ScreenBrush
{
get { return new SolidColorBrush(this.ScreenColor); }
}
public Brush TextBrush
{
get { return new SolidColorBrush(this.TextColor); }
}
public string Title
{
get
{
// Grab the note’s content
string title =
Crypto.Decrypt(this.EncryptedContent, CurrentContext.Password) ?? “”;
// Don’t include more than the first 100 characters, which should be long
// enough, even in landscape with a small font
if (title.Length > 100)
title = title.Substring(0, 100);
// Fold the remaining content into a single line. We can’t use
// Environment.NewLine because it’s rn, whereas newlines inserted from
// a text box are just r
return title.Replace(‘r’, ‘ ‘);
}
}
}
}

[/code]

  • INotifyPropertyChanged has a single member—a PropertyChanged event. If the implementer raises this event at the appropriate time with the name of each property that has changed, data binding takes care of refreshing any targets.
  • The raising of the PropertyChanged event is handled by the OnPropertyChanged helper method. The event handler field is assigned to a handler variable to avoid a potential bug. Without this, if a different thread removed the last handler between the time that the current thread checked for null and performed the invocation, a NullReferenceException would be thrown. (The event handler field becomes null when no more listeners are attached.)
  • Notice that some properties, when changed, raise the PropertyChanged event for an additional property. For example, when EncryptedContent is set to a new value, a PropertyChanged event is raised for the readonly Title property. This is done because the value of Title is based on the value of EncryptedContent, so a change to EncryptedContent may change Title.

INotifyCollectionChanged

Observable collections perform their magic by implementing INotifyCollectionChanged, an interface that is very similar to INotifyPropertyChanged.This interface contains a single CollectionChanged event. It is very rare,however, for people to write their own collection class and implement INotifyCollectionChanged rather than simply using the ObservableCollection class.

The Details Page

The details page, shown in Figure 21.4, appears when the user taps a note in the list box on the main page. This page displays the entire contents of the note and enables the user to edit it, delete it, or email its contents. It also provides access to a per-note settings page that gives control over the note’s colors and text size. Listing 21.10 contains this page’s XAML.

FIGURE 21.4 The details page, shown for a white-on-red note.
FIGURE 21.4 The details page, shown for a white-on-red note.

LISTING 21.10 DetailsPage.xaml—The User Interface for Passwords & Secrets’Details Page

[code]

<phone:PhoneApplicationPage
x:Class=”WindowsPhoneApp.DetailsPage”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:phone=”clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone”
xmlns:shell=”clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone”
xmlns:local=”clr-namespace:WindowsPhoneApp”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”
SupportedOrientations=”PortraitOrLandscape” shell:SystemTray.IsVisible=”True”>
<!– The application bar, with three buttons –>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsVisible=”False”>
<shell:ApplicationBarIconButton Text=”delete”
IconUri=”/Shared/Images/appbar.delete.png”
Click=”DeleteButton_Click”/>
<shell:ApplicationBarIconButton Text=”email”
IconUri=”/Shared/Images/appbar.email.png”
Click=”EmailButton_Click”/>
<shell:ApplicationBarIconButton Text=”settings”
IconUri=”/Shared/Images/appbar.settings.png”
Click=”SettingsButton_Click”/>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
<phone:PhoneApplicationPage.Resources>
<!– A copy of the text box default style with its border removed and
background applied differently. Compare with the style in Program Files
Microsoft SDKsWindows Phonev7.0DesignSystem.Windows.xaml –>

</phone:PhoneApplicationPage.Resources>
<ScrollViewer>
<Grid>
<!– The full-screen text box –>
<TextBox x:Name=”TextBox” InputScope=”Text”
Style=”{StaticResource PhoneTextBox}”
AcceptsReturn=”True” TextWrapping=”Wrap”
GotFocus=”TextBox_GotFocus” LostFocus=”TextBox_LostFocus”/>
<!– The user control –>
<local:LoginControl x:Name=”LoginControl” Closed=”LoginControl_Closed”/>
</Grid>
</ScrollViewer>
</phone:PhoneApplicationPage>

[/code]

The text box that basically occupies the whole screen is given a custom style that removes its border and ensures the desired background color remains visible whether the text box has focus. The style was created by copying the default style from %ProgramFiles%Microsoft SDKsWindows Phonev7.0DesignSystem.Windows.xaml then making a few tweaks.

Listing 21.11 contains the code-behind for this page.

LISTING 21.11 DetailsPage.xaml.cs—The Code-Behind for Passwords & Secrets’Details Page

[code]

using System;
using System.Windows;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Tasks;
namespace WindowsPhoneApp
{
public partial class DetailsPage : PhoneApplicationPage
{
bool navigatingFrom;
string initialText = “”;
public DetailsPage()
{
InitializeComponent();
this.Loaded += DetailsPage_Loaded;
}
void DetailsPage_Loaded(object sender, RoutedEventArgs e)
{
if (CurrentContext.IsLoggedIn)
{
// Automatically show the keyboard for new notes.
// This also gets called when navigating away, hence the extra check
// to make sure we’re only doing this when navigating to the page
if (this.TextBox.Text.Length == 0 && !this.navigatingFrom)
this.TextBox.Focus();
}
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
this.navigatingFrom = true;
base.OnNavigatedFrom(e);
if (this.initialText != this.TextBox.Text)
{
// Automatically save the new content
Note n = Settings.NotesList.Value[Settings.CurrentNoteIndex.Value];
n.EncryptedContent =
Crypto.Encrypt(this.TextBox.Text, CurrentContext.Password) ?? “”;
n.Modified = DateTimeOffset.Now;
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (CurrentContext.IsLoggedIn)
this.LoginControl.Close();
}
void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
this.ApplicationBar.IsVisible = false;
}
void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
this.ApplicationBar.IsVisible = true;
}
void LoginControl_Closed(object sender, EventArgs e)
{
this.ApplicationBar.IsVisible = true;
// Show the note’s contents
Note n = Settings.NotesList.Value[Settings.CurrentNoteIndex.Value];
if (n != null)
{
this.TextBox.Background = n.ScreenBrush;
this.TextBox.Foreground = n.TextBrush;
this.TextBox.FontSize = n.TextSize;
this.initialText = this.TextBox.Text =
Crypto.Decrypt(n.EncryptedContent, CurrentContext.Password) ?? “”;
}
}
// Application bar handlers:
void DeleteButton_Click(object sender, EventArgs e)
{
if (MessageBox.Show(“Are you sure you want to delete this note?”,
“Delete note?”, MessageBoxButton.OKCancel) == MessageBoxResult.OK)
{
Settings.NotesList.Value.Remove(
Settings.NotesList.Value[Settings.CurrentNoteIndex.Value]);
if (this.NavigationService.CanGoBack)
this.NavigationService.GoBack();
}
}
void EmailButton_Click(object sender, EventArgs e)
{
EmailComposeTask launcher = new EmailComposeTask();
launcher.Body = this.TextBox.Text;
launcher.Subject = “Note”;
launcher.Show();
}
void SettingsButton_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(“/SettingsPage.xaml”,
UriKind.Relative));
}
}
}

[/code]

  • This page uses a navigatingFrom flag to check whether the page is in the process of navigating away. That’s because the Loaded event gets raised a second time after OnNavigatedFrom, and applying focus to the text box at this time could cause an unwanted flicker from the on-screen keyboard briefly appearing.
  • The code for the settings page linked to this page is shown in the next chapter, because it is identical to the one used by this app!

A page’s Loaded event is incorrectly raised when navigating away!

This is simply a bug in the current version of Windows Phone.To avoid performance problems, potential flickering, or other issues, consider setting a flag in OnNavigatedFrom that you can check inside Loaded, as done in Listing 21.11.That way, you can be sure that your page-loading logic only runs when the page is actually loading.

The Finished Product

Passwords & Secrets (Encryption & Observable Collections)