ISOイメージを作成することなく、CD-ROMから直接データを読み込んでMD5ハッシュ値を計算するための方法。

MD5を計算するには、System.Security.Cryptography.MD5クラスのComputeHashメソッドを使用することが出来る。 このメソッドは、引数としてSystem.IO.Streamクラスのインスタンスか、Byte()をとる。 CD-ROMのデータを一度Byte()に落としてからComputeHashメソッドに渡すのはムダが多いので、Streamから直接読み込ませる方法を考える。

CdromIsoStreamクラス

まずは、Streamクラスを派生してCD-ROMのデータを直接読み込むためのクラスを作る。

参考資料: CD-ROM から ISO イメージをつくるのは ?

Imports System
Imports System.IO
Imports System.Runtime.InteropServices

Public Class CdromIsoStream

    Inherits System.IO.Stream

    <DllImport("kernel32.dll")> _
    Private Shared Function CreateFile( _
        ByVal lpFileName As String, _
        ByVal dwDesiredAccess As Integer, _
        ByVal dwShareMode As Integer, _
        ByVal lpSecurityAttributes As IntPtr, _
        ByVal dwCreationDisposition As Integer, _
        ByVal dwFLagsAndAttributes As Integer, _
        ByVal hTemplateFile As IntPtr _
    ) As IntPtr
    End Function

    <DllImport("kernel32.dll")> _
    Public Shared Function GetFileSizeEx( _
        ByVal hFile As IntPtr, _
        ByRef lpFileSize As Long _
        ) As Boolean
    End Function

    <DllImport("kernel32.dll")> _
    Private Shared Function ReadFile( _
        ByVal hFile As IntPtr, _
        ByVal buffer As Byte(), _
        ByVal nNumberOfBytesToRead As Integer, _
        ByRef lpNumberOfBytesRead As Integer, _
        ByVal lpOverlapped As IntPtr _
    ) As Integer
    End Function

    <DllImport("kernel32.dll")> _
    Private Shared Function SetFilePointer( _
        ByVal hFile As IntPtr, _
        ByVal lDistanceToMove As Integer, _
        ByRef lpDistanceToMoveHigh As Integer, _
        ByVal dwMoveMethod As FP_MOVE_METHOD _
    ) As Integer
    End Function

    <DllImport("kernel32.dll")> _
    Private Shared Function CloseHandle( _
        ByVal hObject As IntPtr _
    ) As Boolean
    End Function

    <DllImport("kernel32.dll")> _
    Private Shared Function GetLastError() As Integer
    End Function

    Private Shared ReadOnly INVALID_HANDLE_VALUE As IntPtr = New IntPtr(-1)

    Private Const GENERIC_READ As Integer = &H80000000
    Private Const OPEN_EXISTING As Integer = 3

    Private Const FILE_SHARE_READ As Integer = &H1

    Private Const NO_ERROR As Integer = 0

    Private Enum FP_MOVE_METHOD As Integer

        FILE_BEGIN = 0
        FILE_CURRENT = 1
        FILE_END = 2

    End Enum

    Private hFile As IntPtr = IntPtr.Zero

    ' ドライブレターを指定しないでCD-ROMドライブを開くためのコンストラクタ
    Public Sub New()

        hFile = CreateFile("\\?\CdRom0", GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero)

        If INVALID_HANDLE_VALUE.Equals(hFile) Then Throw New IOException("New() failed")

    End Sub

    ' ドライブレターを指定してCD-ROMドライブを開くためのコンストラクタ
    Public Sub New(ByVal drive As String)

        hFile = CreateFile(String.Format("\\.\{0}", drive), GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero)

        If INVALID_HANDLE_VALUE.Equals(hFile) Then Throw New IOException("New() failed")

    End Sub

    Protected Overrides Sub Finalize()

        If Not IntPtr.Zero.Equals(hFile) Then Close()

    End Sub

    Public Overrides ReadOnly Property CanRead() As Boolean
        Get
            Return True
        End Get
    End Property

    Public Overrides ReadOnly Property CanSeek() As Boolean
        Get
            Return True
        End Get
    End Property

    Public Overrides ReadOnly Property CanWrite() As Boolean
        Get
            Return False
        End Get
    End Property

    Public Overridable ReadOnly Property Handle() As IntPtr
        Get
            Return hFile
        End Get
    End Property

    Public Overrides Sub Flush()

        ' 何もしない
        'BOOL FlushFileBuffers(HANDLE hFile);

    End Sub

    Public Overrides ReadOnly Property Length() As Long

        Get

            Dim len As Long = 0

            ' なぜかGetFileSizeExではうまく取得できないが、
            ' Lengthを使用することがないので放置
            If GetFileSizeEx(hFile, len) Then

                Return len

            Else

                Dim err As Integer = GetLastError()

                Return 0

            End If

            ' シークをサポートしていないので失敗する
            'Dim pos As Long = Me.Position

            'Dim len As Long = Seek(0, SeekOrigin.End)

            'Seek(pos, SeekOrigin.Begin)

            'Return len

        End Get

    End Property

    Public Overrides Property Position() As Long

        Get

            Dim posHigh As Integer = 0
            Dim posLow As Integer = 0

            posLow = SetFilePointer(hFile, posLow, posHigh, FP_MOVE_METHOD.FILE_CURRENT)

            If posLow = &HFFFFFFFF And GetLastError() <> NO_ERROR Then

                Throw New IOException("Position() failed")

            End If

            Return CLng(posLow) Or CLng(posHigh) * &H100000000

        End Get

        Set(ByVal Value As Long)

            Seek(Value, SeekOrigin.Begin)

        End Set

    End Property

    Public Overrides Function Read(ByVal buffer() As Byte, ByVal offset As Integer, ByVal count As Integer) As Integer

        Dim ret As Integer = 0
        Dim readBytes As Integer = 0

        ' offsetの設定は未対応
        If offset <> 0 Then Throw New ArgumentException

        ret = ReadFile(hFile, buffer, count, readBytes, IntPtr.Zero)

        If ret <> 0 Then

            Return readBytes

        Else

            Throw New IOException("Read() failed")

        End If

    End Function

    Public Overrides Function Seek(ByVal offset As Long, ByVal origin As System.IO.SeekOrigin) As Long

        Dim moveMethod As FP_MOVE_METHOD

        Select Case origin

            Case SeekOrigin.Begin
                moveMethod = FP_MOVE_METHOD.FILE_BEGIN

            Case SeekOrigin.Current
                moveMethod = FP_MOVE_METHOD.FILE_CURRENT

            Case SeekOrigin.End
                moveMethod = FP_MOVE_METHOD.FILE_END

            Case Else
                Throw New ArgumentOutOfRangeException("origin")

        End Select

        Dim posHigh As Integer = CInt((offset \ &H10000000) And &HFFFFFFFF)
        Dim posLow As Integer = CInt(offset And &HFFFFFFFF)

        posLow = SetFilePointer(hFile, posLow, posHigh, moveMethod)

        Dim err As Integer = GetLastError()

        If posLow = &HFFFFFFFF And err <> NO_ERROR Then

            Throw New IOException("Seek() failed")

        End If

        Return CLng(posLow) Or CLng(posHigh) * &H100000000

    End Function

    Public Overrides Sub SetLength(ByVal value As Long)

        Throw New IOException("read only")

    End Sub

    Public Overrides Sub Write(ByVal buffer() As Byte, ByVal offset As Integer, ByVal count As Integer)

        Throw New IOException("read only")

    End Sub

    Public Overrides Sub Close()

        If CloseHandle(hFile) Then

            hFile = IntPtr.Zero

        Else

            Throw New IOException("Close() failed")

        End If

        MyBase.Close()

    End Sub

End Class
 _
    Private Shared Function CreateFile( _
        ByVal lpFileName As String, _
        ByVal dwDesiredAccess As Integer, _
        ByVal dwShareMode As Integer, _
        ByVal lpSecurityAttributes As IntPtr, _
        ByVal dwCreationDisposition As Integer, _
        ByVal dwFLagsAndAttributes As Integer, _
        ByVal hTemplateFile As IntPtr _
    ) As IntPtr
    End Function

     _
    Public Shared Function GetFileSizeEx( _
        ByVal hFile As IntPtr, _
        ByRef lpFileSize As Long _
        ) As Boolean
    End Function

     _
    Private Shared Function ReadFile( _
        ByVal hFile As IntPtr, _
        ByVal buffer As Byte(), _
        ByVal nNumberOfBytesToRead As Integer, _
        ByRef lpNumberOfBytesRead As Integer, _
        ByVal lpOverlapped As IntPtr _
    ) As Integer
    End Function

     _
    Private Shared Function SetFilePointer( _
        ByVal hFile As IntPtr, _
        ByVal lDistanceToMove As Integer, _
        ByRef lpDistanceToMoveHigh As Integer, _
        ByVal dwMoveMethod As FP_MOVE_METHOD _
    ) As Integer
    End Function

     _
    Private Shared Function CloseHandle( _
        ByVal hObject As IntPtr _
    ) As Boolean
    End Function

     _
    Private Shared Function GetLastError() As Integer
    End Function

    Private Shared ReadOnly INVALID_HANDLE_VALUE As IntPtr = New IntPtr(-1)

    Private Const GENERIC_READ As Integer = &H80000000
    Private Const OPEN_EXISTING As Integer = 3

    Private Const FILE_SHARE_READ As Integer = &H1

    Private Const NO_ERROR As Integer = 0

    Private Enum FP_MOVE_METHOD As Integer

        FILE_BEGIN = 0
        FILE_CURRENT = 1
        FILE_END = 2

    End Enum

    Private hFile As IntPtr = IntPtr.Zero

    ' ドライブレターを指定しないでCD-ROMドライブを開くためのコンストラクタ
    Public Sub New()

        hFile = CreateFile("\\?\CdRom0", GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero)

        If INVALID_HANDLE_VALUE.Equals(hFile) Then Throw New IOException("New() failed")

    End Sub

    ' ドライブレターを指定してCD-ROMドライブを開くためのコンストラクタ
    Public Sub New(ByVal drive As String)

        hFile = CreateFile(String.Format("\\.\{0}", drive), GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero)

        If INVALID_HANDLE_VALUE.Equals(hFile) Then Throw New IOException("New() failed")

    End Sub

    Protected Overrides Sub Finalize()

        If Not IntPtr.Zero.Equals(hFile) Then Close()

    End Sub

    Public Overrides ReadOnly Property CanRead() As Boolean
        Get
            Return True
        End Get
    End Property

    Public Overrides ReadOnly Property CanSeek() As Boolean
        Get
            Return True
        End Get
    End Property

    Public Overrides ReadOnly Property CanWrite() As Boolean
        Get
            Return False
        End Get
    End Property

    Public Overridable ReadOnly Property Handle() As IntPtr
        Get
            Return hFile
        End Get
    End Property

    Public Overrides Sub Flush()

        ' 何もしない
        'BOOL FlushFileBuffers(HANDLE hFile);

    End Sub

    Public Overrides ReadOnly Property Length() As Long

        Get

            Dim len As Long = 0

            ' なぜかGetFileSizeExではうまく取得できないが、
            ' Lengthを使用することがないので放置
            If GetFileSizeEx(hFile, len) Then

                Return len

            Else

                Dim err As Integer = GetLastError()

                Return 0

            End If

            ' シークをサポートしていないので失敗する
            'Dim pos As Long = Me.Position

            'Dim len As Long = Seek(0, SeekOrigin.End)

            'Seek(pos, SeekOrigin.Begin)

            'Return len

        End Get

    End Property

    Public Overrides Property Position() As Long

        Get

            Dim posHigh As Integer = 0
            Dim posLow As Integer = 0

            posLow = SetFilePointer(hFile, posLow, posHigh, FP_MOVE_METHOD.FILE_CURRENT)

            If posLow = &HFFFFFFFF And GetLastError() <> NO_ERROR Then

                Throw New IOException("Position() failed")

            End If

            Return CLng(posLow) Or CLng(posHigh) * &H100000000

        End Get

        Set(ByVal Value As Long)

            Seek(Value, SeekOrigin.Begin)

        End Set

    End Property

    Public Overrides Function Read(ByVal buffer() As Byte, ByVal offset As Integer, ByVal count As Integer) As Integer

        Dim ret As Integer = 0
        Dim readBytes As Integer = 0

        ' offsetの設定は未対応
        If offset <> 0 Then Throw New ArgumentException

        ret = ReadFile(hFile, buffer, count, readBytes, IntPtr.Zero)

        If ret <> 0 Then

            Return readBytes

        Else

            Throw New IOException("Read() failed")

        End If

    End Function

    Public Overrides Function Seek(ByVal offset As Long, ByVal origin As System.IO.SeekOrigin) As Long

        Dim moveMethod As FP_MOVE_METHOD

        Select Case origin

            Case SeekOrigin.Begin
                moveMethod = FP_MOVE_METHOD.FILE_BEGIN

            Case SeekOrigin.Current
                moveMethod = FP_MOVE_METHOD.FILE_CURRENT

            Case SeekOrigin.End
                moveMethod = FP_MOVE_METHOD.FILE_END

            Case Else
                Throw New ArgumentOutOfRangeException("origin")

        End Select

        Dim posHigh As Integer = CInt((offset \ &H10000000) And &HFFFFFFFF)
        Dim posLow As Integer = CInt(offset And &HFFFFFFFF)

        posLow = SetFilePointer(hFile, posLow, posHigh, moveMethod)

        Dim err As Integer = GetLastError()

        If posLow = &HFFFFFFFF And err <> NO_ERROR Then

            Throw New IOException("Seek() failed")

        End If

        Return CLng(posLow) Or CLng(posHigh) * &H100000000

    End Function

    Public Overrides Sub SetLength(ByVal value As Long)

        Throw New IOException("read only")

    End Sub

    Public Overrides Sub Write(ByVal buffer() As Byte, ByVal offset As Integer, ByVal count As Integer)

        Throw New IOException("read only")

    End Sub

    Public Overrides Sub Close()

        If CloseHandle(hFile) Then

            hFile = IntPtr.Zero

        Else

            Throw New IOException("Close() failed")

        End If

        MyBase.Close()

    End Sub

End Class]]>

MD5の計算

前述のクラスを使うことで、CD-ROMのデータをStreamとして読み込めるようになったので、早速ComputeHashメソッドにこのインスタンスを渡してMD5を計算させる。 ここでは計算したMD5に対して、Convert.ToBase64Stringメソッドを使用してBase64エンコードしている。

Class ComputeCDRomMD5

    Public Shared Sub Main()

        Console.WriteLine("MD5 Hash(Base64) : {0}", ComputeMD5HashBase64("I:"))

    End Sub

    Private Shared Function ComputeMD5HashBase64(ByVal drive As String) As String

        Dim stream As CdromIsoStream = Nothing

        Try

            ' CD-ROMデバイスを開く
            stream = New CdromIsoStream(drive)

            ' MD5ハッシュ値を計算する
            Dim md5 As System.Security.Cryptography.MD5 = System.Security.Cryptography.MD5.Create()

            ' Base64
            Return Convert.ToBase64String(md5.ComputeHash(stream))

        Catch ex As Exception

            Console.WriteLine(ex.Message)

        Finally

            If Not stream Is Nothing Then stream.Close()

        End Try

        Return String.Empty

    End Function

End Class