ファイルにパスワードを付加する安全な方法

パスワードのハッシュ化:

パスワードを直接使うと長さや内容に依存するため、
SHA-256でハッシュ化し、
固定長のハッシュ値をもとに挿入位置や間隔を決定する。

ランダムバイトの挿入:

挿入するバイトをランダムに生成し、
パスワードに依存したシードを使うことで予測を難しくする。

一貫性と柔軟性:
ハッシュ値から開始位置、間隔、バイト数を動的に決定し、
ファイル全体に適用する。

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

class FileProtector
{
    /// <summary>
    /// パスワードをSHA-256でハッシュ化し、バイト配列として返す。
    /// </summary>
    /// <param name="password_">ユーザーが設定したパスワード</param>
    /// <returns>ハッシュ化されたバイト配列(256ビット=32バイト)</returns>
    private static byte[] hash_password(string password_)
    {
        using (var sha256 = SHA256.Create())
        {
            // パスワードをUTF-8でバイト配列に変換し、ハッシュ化
            return sha256.ComputeHash(Encoding.UTF8.GetBytes(password_));
        }
    }

    /// <summary>
    /// 元のファイルデータにパスワードに基づくランダムバイトを挿入し、新しいデータを作成する。
    /// </summary>
    /// <param name="file_data_">元のファイルデータ</param>
    /// <param name="hash_">パスワードのハッシュ値</param>
    /// <param name="new_file_data_">バイトが挿入された新しいファイルデータ</param>
    private static void insert_bytes(byte[] file_data_, byte[] hash_, out byte[] new_file_data_)
    {
        // ハッシュ値から挿入の開始位置、間隔、バイト数を決定
        uint start_pos = BitConverter.ToUInt32(hash_, 0) % (uint)file_data_.Length; // 開始位置(ファイルサイズで割った余り)
        ushort interval = (ushort)(BitConverter.ToUInt16(hash_, 4) % 10 + 1);      // 挿入間隔(1~10バイト)
        ushort byte_count = (ushort)(BitConverter.ToUInt16(hash_, 6) % 5 + 1);     // 挿入バイト数(1~5バイト)

        // ランダムバイト生成のためのシード(ハッシュ値から取得)
        int seed = BitConverter.ToInt32(hash_, 8);
        var random = new Random(seed);

        // 新しいファイルデータをメモリストリームに構築
        using (var ms = new MemoryStream())
        {
            int pos = 0; // 現在の処理位置
            while (pos < file_data_.Length)
            {
                // 開始位置まで元のデータをそのままコピー
                if (pos < start_pos)
                {
                    int copy_length = (int)Math.Min(start_pos - pos, file_data_.Length - pos);
                    ms.Write(file_data_, pos, copy_length);
                    pos += copy_length;
                }
                else
                {
                    // 挿入位置にランダムバイトを追加
                    byte[] random_bytes = new byte[byte_count];
                    random.NextBytes(random_bytes);
                    ms.Write(random_bytes, 0, byte_count);

                    // 次の挿入位置まで元のデータをコピー
                    int next_pos = pos + interval;
                    if (next_pos < file_data_.Length)
                    {
                        ms.Write(file_data_, pos, interval);
                        pos = next_pos;
                    }
                    else
                    {
                        ms.Write(file_data_, pos, file_data_.Length - pos);
                        pos = file_data_.Length;
                    }
                }
            }
            new_file_data_ = ms.ToArray();
        }
    }

    /// <summary>
    /// バイトが挿入されたファイルデータから無関係なバイトを除去し、元のデータに戻す。
    /// </summary>
    /// <param name="file_data_">バイトが挿入されたファイルデータ</param>
    /// <param name="hash_">パスワードのハッシュ値</param>
    /// <param name="original_file_data_">復元された元のファイルデータ</param>
    private static void remove_bytes(byte[] file_data_, byte[] hash_, out byte[] original_file_data_)
    {
        // ハッシュ値から挿入時の開始位置、間隔、バイト数を再計算
        uint start_pos = BitConverter.ToUInt32(hash_, 0) % (uint)file_data_.Length;
        ushort interval = (ushort)(BitConverter.ToUInt16(hash_, 4) % 10 + 1);
        ushort byte_count = (ushort)(BitConverter.ToUInt16(hash_, 6) % 5 + 1);

        // 元のデータをメモリストリームに構築
        using (var ms = new MemoryStream())
        {
            int pos = 0; // 現在の処理位置
            while (pos < file_data_.Length)
            {
                // 開始位置までデータをそのままコピー
                if (pos < start_pos)
                {
                    int copy_length = (int)Math.Min(start_pos - pos, file_data_.Length - pos);
                    ms.Write(file_data_, pos, copy_length);
                    pos += copy_length;
                }
                else
                {
                    // 挿入されたバイトをスキップ
                    pos += byte_count;

                    // 次の挿入位置までデータをコピー
                    int next_pos = pos + interval;
                    if (next_pos < file_data_.Length)
                    {
                        ms.Write(file_data_, pos, interval);
                        pos = next_pos;
                    }
                    else
                    {
                        ms.Write(file_data_, pos, file_data_.Length - pos);
                        pos = file_data_.Length;
                    }
                }
            }
            original_file_data_ = ms.ToArray();
        }
    }

    /// <summary>
    /// パスワードを使ってファイルを保存する。
    /// </summary>
    /// <param name="file_path_">保存先のファイルパス</param>
    /// <param name="password_">設定するパスワード</param>
    /// <param name="file_data_">保存する元のファイルデータ</param>
    public static void save_file(string file_path_, string password_, byte[] file_data_)
    {
        byte[] hash = hash_password(password_); // パスワードをハッシュ化
        insert_bytes(file_data_, hash, out byte[] new_file_data); // バイトを挿入
        File.WriteAllBytes(file_path_, new_file_data); // ファイルに書き込み
    }

    /// <summary>
    /// パスワードを使ってファイルを開く。
    /// </summary>
    /// <param name="file_path_">読み込むファイルパス</param>
    /// <param name="password_">設定されたパスワード</param>
    /// <returns>復元された元のファイルデータ</returns>
    public static byte[] load_file(string file_path_, string password_)
    {
        byte[] hash = hash_password(password_); // パスワードをハッシュ化
        byte[] file_data = File.ReadAllBytes(file_path_); // ファイルを読み込み
        remove_bytes(file_data, hash, out byte[] original_file_data); // バイトを除去
        return original_file_data;
    }

    // 使用例
    static void Main()
    {
        string file_path = "test.bin";
        string password = "MySecretPass123";
        byte[] original_data = Encoding.UTF8.GetBytes("これはテストデータです。");

        // ファイル保存
        save_file(file_path, password, original_data);
        Console.WriteLine("ファイルが保存されました。");

        // ファイル読み込み
        byte[] loaded_data = load_file(file_path, password);
        Console.WriteLine("読み込んだデータ: " + Encoding.UTF8.GetString(loaded_data));
    }
}

hash_password 関数

パスワードをSHA-256でハッシュ化し、
32バイトの固定長データに変換する。

これにより、パスワードの長さや内容に依存せず一貫した値を生成する。

insert_bytes 関数

元のファイルデータにランダムバイトを挿入する。

開始位置(start_pos)、間隔(interval)、バイト数(byte_count)を
ハッシュ値から決定する。

ランダムバイトはパスワード依存のシードで生成し、予測を困難にする。

remove_bytes 関数

挿入されたバイトを除去し、元のデータを復元する。

正しいパスワード(ハッシュ値)でないと位置や数が一致せず、
データが壊れる。

save_file および load_file 関数

ファイルの保存と読み込みを簡潔に実装。

ユーザーが直接呼び出せるインターフェース。

《 複数のファイルへの対応 》


パスワードをSHA-256でハッシュ化し、
そのハッシュ値から挿入位置や値を決定している場合、
同じパスワードでは常に同じハッシュ値が生成され、
結果として同じ位置と値が使用されてしまう。

これを回避するため、以下のようにソルト(salt)を導入する。

ソルトの導入:

ファイルごとにランダムなソルトを生成し、ファイルの先頭に保存する。

ハッシュ化:

パスワードとソルトを組み合わせてSHA-256でハッシュ化し、
そのハッシュ値から挿入位置、間隔、バイト数を決定する。

利点:

同じパスワードでも、ソルトが異なるため
各ファイルごとに異なるハッシュ値が生成され、挿入位置や値が異なる。

これにより、バイナリ配列の比較によるパスワードの割り出しを防ぐ。

実装の詳細ソルトの生成と保存:

ファイル保存時に、16バイトのランダムなソルトを生成する。

このソルトをファイルの先頭に保存し、その後に元のファイルデータを続ける。

ハッシュ化:

パスワードとソルトを組み合わせてSHA-256でハッシュ化する。

生成されたハッシュ値から、挿入の開始位置、間隔、バイト数を決定する。

バイトの挿入と除去:保存時:

ソルトを先頭に追加し、ハッシュ値に基づいて
ランダムバイトを挿入したデータを作成する。

読み込み時:

ファイルの先頭からソルトを読み取り、パスワードと組み合わせてハッシュ化し、
挿入されたバイトを除去して元のデータを復元する。

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

class FileProtector
{
    private const int SALT_SIZE = 16; // ソルトのサイズ(バイト)

    // パスワードとソルトを組み合わせてハッシュ化
    private static byte[] HashPasswordWithSalt(string password, byte[] salt)
    {
        using (var sha256 = SHA256.Create())
        {
            byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
            byte[] combined = new byte[passwordBytes.Length + salt.Length];
            Buffer.BlockCopy(passwordBytes, 0, combined, 0, passwordBytes.Length);
            Buffer.BlockCopy(salt, 0, combined, passwordBytes.Length, salt.Length);
            return sha256.ComputeHash(combined);
        }
    }

    // ソルトとバイトを挿入
    private static void InsertBytesWithSalt(byte[] fileData, byte[] hash, byte[] salt, out byte[] newFileData)
    {
        uint startPos = BitConverter.ToUInt32(hash, 0) % (uint)fileData.Length;
        ushort interval = (ushort)(BitConverter.ToUInt16(hash, 4) % 10 + 1);
        ushort byteCount = (ushort)(BitConverter.ToUInt16(hash, 6) % 5 + 1);
        int seed = BitConverter.ToInt32(hash, 8);
        var random = new Random(seed);

        using (var ms = new MemoryStream())
        {
            ms.Write(salt, 0, salt.Length); // ソルトを先頭に追加
            int pos = 0;
            while (pos < fileData.Length)
            {
                if (pos < startPos)
                {
                    int copyLength = (int)Math.Min(startPos - pos, fileData.Length - pos);
                    ms.Write(fileData, pos, copyLength);
                    pos += copyLength;
                }
                else
                {
                    byte[] randomBytes = new byte[byteCount];
                    random.NextBytes(randomBytes);
                    ms.Write(randomBytes, 0, byteCount);
                    int nextPos = pos + interval;
                    if (nextPos < fileData.Length)
                    {
                        ms.Write(fileData, pos, interval);
                        pos = nextPos;
                    }
                    else
                    {
                        ms.Write(fileData, pos, fileData.Length - pos);
                        pos = fileData.Length;
                    }
                }
            }
            newFileData = ms.ToArray();
        }
    }

    // バイトを除去
    private static void RemoveBytesWithSalt(byte[] fileData, byte[] hash, out byte[] originalFileData)
    {
        uint startPos = BitConverter.ToUInt32(hash, 0) % (uint)(fileData.Length - SALT_SIZE);
        ushort interval = (ushort)(BitConverter.ToUInt16(hash, 4) % 10 + 1);
        ushort byteCount = (ushort)(BitConverter.ToUInt16(hash, 6) % 5 + 1);

        using (var ms = new MemoryStream())
        {
            int pos = SALT_SIZE; // ソルトの後から開始
            while (pos < fileData.Length)
            {
                if (pos < startPos + SALT_SIZE)
                {
                    int copyLength = (int)Math.Min(startPos + SALT_SIZE - pos, fileData.Length - pos);
                    ms.Write(fileData, pos, copyLength);
                    pos += copyLength;
                }
                else
                {
                    pos += byteCount; // 挿入バイトをスキップ
                    int nextPos = pos + interval;
                    if (nextPos < fileData.Length)
                    {
                        ms.Write(fileData, pos, interval);
                        pos = nextPos;
                    }
                    else
                    {
                        ms.Write(fileData, pos, fileData.Length - pos);
                        pos = fileData.Length;
                    }
                }
            }
            originalFileData = ms.ToArray();
        }
    }

    // ファイル保存
    public static void SaveFile(string filePath, string password, byte[] fileData)
    {
        byte[] salt = new byte[SALT_SIZE];
        using (var rng = new RNGCryptoServiceProvider())
        {
            rng.GetBytes(salt); // ランダムなソルト生成
        }
        byte[] hash = HashPasswordWithSalt(password, salt);
        InsertBytesWithSalt(fileData, hash, salt, out byte[] newFileData);
        File.WriteAllBytes(filePath, newFileData);
    }

    // ファイル読み込み
    public static byte[] LoadFile(string filePathasc = File.ReadAllBytes(filePath);
    byte[] salt = new byte[SALT_SIZE];
    Buffer.BlockCopy(fileData, 0, salt, 0, SALT_SIZE);
    byte[] hash = HashPasswordWithSalt(password, salt);
    RemoveBytesWithSalt(fileData, hash, out byte[] originalData);
    return originalData;
}

// 使用例
static void Main()
{
    string filePath = "test.bin";
    string password = "MySecretPass123";
    byte[] originalData = Encoding.UTF8.GetBytes("テストデータ");

    SaveFile(filePath, password, originalData);
    byte[] loadedData = LoadFile(filePath, password);
    Console.WriteLine("復元データ: " + Encoding.UTF8.GetString(loadedData));
}

hash_password 関数

パスワードをSHA-256でハッシュ化し、
32バイトの固定長データに変換する。

これにより、パスワードの長さや内容に依存せず一貫した値を生成する。

insert_bytes 関数

元のファイルデータにランダムバイトを挿入する。

開始位置(start_pos)、間隔(interval)、バイト数(byte_count)を
ハッシュ値から決定する。

ランダムバイトはパスワード依存のシードで生成し、予測を困難にする。

remove_bytes 関数

挿入されたバイトを除去し、元のデータを復元する。

正しいパスワード(ハッシュ値)でないと位置や数が一致せず、データが壊れる。

save_file および load_file 関数

ファイルの保存と読み込みを簡潔に実装している。

ユーザーが直接呼び出せるインターフェース。

注意点と改善案ファイルサイズの増加: 挿入バイトが多いと
ファイルサイズが膨らむ。

intervalやbyte_countを調整することで抑えられる。


セキュリティ向上:

ソルトにより、同じパスワードでも
ファイルごとに異なるハッシュ値が生成され、
挿入位置と値が異なる。

バイナリ比較の防止:

ファイルごとに異なるソルトを使用することで、
バイナリ配列の比較によるパスワードの割り出しが困難になる。

実用性:

ソルトはファイルの先頭に固定長(16バイト)で保存されるため、
処理がシンプルで実装しやすい。

《 暗号化 》


暗号化

AES(Advanced Encryption Standard)のCBCモードを使用して、
ファイル全体をパスワードに基づいて暗号化する。

パスワードから鍵を生成し、ランダムな初期化ベクトル(IV)を付加する。

バイト挿入

暗号化されたデータに対して、ランダムなソルトを生成し、
パスワードとソルトを組み合わせたハッシュ値に基づいて
ランダムバイトを挿入する。

ソルトとIVはファイルの先頭に保存され、復号時に利用される。

復号化

保存されたソルトとIVを使用して、挿入されたバイトを除去し、
AESでデータを復号化する。

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

class FileProtector
{
    private const int SALT_SIZE = 16; // ソルトのサイズ(バイト)
    private const int IV_SIZE = 16;   // AESのIVのサイズ(バイト)

    // パスワードからAESの鍵を生成
    private static byte[] GenerateKeyFromPassword(string password)
    {
        using (var sha256 = SHA256.Create())
        {
            return sha256.ComputeHash(Encoding.UTF8.GetBytes(password));
        }
    }

    // パスワードとソルトを組み合わせてハッシュ化
    private static byte[] HashPasswordWithSalt(string password, byte[] salt)
    {
        using (var sha256 = SHA256.Create())
        {
            byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
            byte[] combined = new byte[passwordBytes.Length + salt.Length];
            Buffer.BlockCopy(passwordBytes, 0, combined, 0, passwordBytes.Length);
            Buffer.BlockCopy(salt, 0, combined, passwordBytes.Length, salt.Length);
            return sha256.ComputeHash(combined);
        }
    }

    // AESでファイルを暗号化
    private static byte[] EncryptFile(byte[] fileData, byte[] key, byte[] iv)
    {
        using (var aes = Aes.Create())
        {
            aes.Key = key;
            aes.IV = iv;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;

            using (var ms = new MemoryStream())
            {
                using (var cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(fileData, 0, fileData.Length);
                    cs.FlushFinalBlock();
                }
                return ms.ToArray();
            }
        }
    }

    // AESでファイルを復号化
    private static byte[] DecryptFile(byte[] encryptedData, byte[] key, byte[] iv)
    {
        using (var aes = Aes.Create())
        {
            aes.Key = key;
            aes.IV = iv;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;

            using (var ms = new MemoryStream())
            {
                using (var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(encryptedData, 0, encryptedData.Length);
                    cs.FlushFinalBlock();
                }
                return ms.ToArray();
            }
        }
    }

    // ソルトとバイトを挿入
    private static void InsertBytesWithSalt(byte[] encryptedData, byte[] hash, byte[] salt, byte[] iv, out byte[] newFileData)
    {
        uint startPos = BitConverter.ToUInt32(hash, 0) % (uint)encryptedData.Length;
        ushort interval = (ushort)(BitConverter.ToUInt16(hash, 4) % 10 + 1);
        ushort byteCount = (ushort)(BitConverter.ToUInt16(hash, 6) % 5 + 1);
        int seed = BitConverter.ToInt32(hash, 8);
        var random = new Random(seed);

        using (var ms = new MemoryStream())
        {
            ms.Write(salt, 0, salt.Length); // ソルトを先頭に追加
            ms.Write(iv, 0, iv.Length);     // IVを追加
            int pos = 0;
            while (pos < encryptedData.Length)
            {
                if (pos < startPos)
                {
                    int copyLength = (int)Math.Min(startPos - pos, encryptedData.Length - pos);
                    ms.Write(encryptedData, pos, copyLength);
                    pos += copyLength;
                }
                else
                {
                    byte[] randomBytes = new byte[byteCount];
                    random.NextBytes(randomBytes);
好像 ms.Write(randomBytes, 0, byteCount);
                    int nextPos = pos + interval;
                    if (nextPos < encryptedData.Length)
                    {
                        ms.Write(encryptedData, pos, interval);
                        pos = nextPos;
                    }
                    else
                    {
                        ms.Write(encryptedData, pos, encryptedData.Length - pos);
                        pos = encryptedData.Length;
                    }
                }
            }
            newFileData = ms.ToArray();
        }
    }

    // バイトを除去
    private static void RemoveBytesWithSalt(byte[] fileData, byte[] hash, out byte[] encryptedData)
    {
        uint startPos = BitConverter.ToUInt32(hash, 0) % (uint)(fileData.Length - SALT_SIZE - IV_SIZE);
        ushort interval = (ushort)(BitConverter.ToUInt16(hash, 4) % 10 + 1);
        ushort byteCount = (ushort)(BitConverter.ToUInt16(hash, 6) % 5 + 1);

        using (var ms = new MemoryStream())
        {
            int pos = SALT_SIZE + IV_SIZE; // ソルトとIVの後から開始
            while (pos < fileData.Length)
            {
                if (pos < startPos + SALT_SIZE + IV_SIZE)
                {
                    int copyLength = (int)Math.Min(startPos + SALT_SIZE + IV_SIZE - pos, fileData.Length - pos);
                    ms.Write(fileData, pos, copyLength);
                    pos += copyLength;
                }
                else
                {
                    pos += byteCount; // 挿入バイトをスキップ
                    int nextPos = pos + interval;
                    if (nextPos < fileData.Length)
                    {
                        ms.Write(fileData, pos, interval);
                        pos = nextPos;
                    }
                    else
                    {
                        ms.Write(fileData, pos, fileData.Length - pos);
                        pos = fileData.Length;
                    }
                }
            }
            encryptedData = ms.ToArray();
        }
    }

    // ファイル保存
    public static void SaveFile(string filePath, string password, byte[] fileData)
    {
        byte[] salt = new byte[SALT_SIZE];
        byte[] iv = new byte[IV_SIZE];
        using (var rng = new RNGCryptoServiceProvider())
        {
            rng.GetBytes(salt); // ランダムなソルト生成
            rng.GetBytes(iv);   // ランダムなIV生成
        }
        byte[] key = GenerateKeyFromPassword(password);
        byte[] encryptedData = EncryptFile(fileData, key, iv);
        byte[] hash = HashPasswordWithSalt(password, salt);
        InsertBytesWithSalt(encryptedData, hash, salt, iv, out byte[] newFileData);
        File.WriteAllBytes(filePath, newFileData);
    }

    // ファイル読み込み
    public static byte[] LoadFile(string filePath, string password)
    {
        byte[] fileData = File.ReadAllBytes(filePath);
        byte[] salt = new byte[SALT_SIZE];
        byte[] iv = new byte[IV_SIZE];
        Buffer.BlockCopy(fileData, 0, salt, 0, SALT_SIZE);
        Buffer.BlockCopy(fileData, SALT_SIZE, iv, 0, IV_SIZE);
        byte[] hash = HashPasswordWithSalt(password, salt);
        byte[] encryptedData;
        RemoveBytesWithSalt(fileData, hash, out encryptedData);
        byte[] key = GenerateKeyFromPassword(password);
        return DecryptFile(encryptedData, key, iv);
    }

    // 使用例
    static void Main()
    {
        string filePath = "test.bin";
        string password = "MySecretPass123";
        byte[] originalData = Encoding.UTF8.GetBytes("テストデータ");

        SaveFile(filePath, password, originalData);
        byte[] loadedData = LoadFile(filePath, password);
        Console.WriteLine("復元データ: " + Encoding.UTF8.GetString(loadedData));
    }
}

(1)暗号化の実装

GenerateKeyFromPassword

パスワードをSHA-256でハッシュ化し、
AESの鍵(32バイト)を生成する。

EncryptFile

AES-CBCモードでデータを暗号化する。

IV(初期化ベクトル)はランダムに生成され、
暗号化されたデータに影響を与える。

DecryptFile

保存されたIVと鍵を使用してデータを復号化する。

(2)バイト挿入の実装

HashPasswordWithSalt

パスワードとランダムなソルトを組み合わせてSHA-256でハッシュ化し、
バイト挿入の位置や数を決定する。

InsertBytesWithSalt

ソルトとIVをファイルの先頭に保存する。

ハッシュ値から開始位置(startPos)、挿入間隔(interval)、
挿入バイト数(byteCount)を計算する。

指定された位置にランダムなバイトを挿入し、
元のバイナリとの類似性を排除する。

RemoveBytesWithSalt

挿入されたバイトをハッシュ値に基づいて除去し、
暗号化されたデータを復元する。

(3)ファイル保存と読み込み

SaveFile

ソルトとIVを生成。

データを暗号化し、バイト挿入を適用してファイルに保存する。

LoadFile

ファイルからソルトとIVを読み取り、バイトを除去し、
復号化して元のデータを返す。

特徴と利点セキュリティ

AES-CBCによる暗号化でデータが保護され、
バイト挿入により元のバイナリとの一致部分が残らない。

再現性

同じパスワードとファイルでも、
ソルトがランダムであるため毎回異なる出力が生成される。

完全性

ソルトとIVをファイルに保存することで、
復号時に必要な情報が保持される。


いいなと思ったら応援しよう!

コメント

ログイン または 会員登録 するとコメントできます。
ファイルにパスワードを付加する安全な方法|古井和雄
word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1