ISOイメージを作成することなく、CD-ROMから直接データを読み込んでMD5ハッシュ値を計算するための方法。
MD5を計算するには、System.Security.Cryptography.MD5クラスのComputeHashメソッドを使用することが出来る。 このメソッドは、引数としてSystem.IO.Streamクラスのインスタンスか、Byte()をとる。 CD-ROMのデータを一度Byte()に落としてからComputeHashメソッドに渡すのはムダが多いので、Streamから直接読み込ませる方法を考える。
まずは、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]]>
前述のクラスを使うことで、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