Other threads have covered that file.encrypt on vb is not supported on W7HE. however i cant find a simple work around for it, and the only other function i know for encryption is using:
Imports system.security.Cryptography
and then doing along the lines of
Dim rsa As New RSACryptoServiceProvider
Dim encoder As New UTF8Encoding
etc,etc
however that only does strings, i need to encrypt the file as a whole.
Any ideas would be greatly appreciated
file.encrypt uses EFS, which is actually transparent encryption; the user account stores the master certificate, so anyone logged in under that account can access the file contents in any application. Depending on what you need, that might not be what you want at all. If you want to replicate EFS behavior for your app alone, it's as simple as storing a master password somewhere in the user's private files, split your data file into 'key' and 'data' sections, and encrypt the key with the master password. (Real EFS has some subtleties: It can encrypt the password separately for many different users, so it allows different users access per-file, giving a recovery option in case the main user's password is lost. It also uses the user's password to encrypt the master password, so if anyone changes the password they won't be able to open files.)
The basic method is just prompt the user for the password and use that to encrypt or decrypt the entire file at once rather than a string at a time. Serialize your file however you would unencrypted, then pass that whole byte array to something similar to EncryptStringToBytes_Aes. The one and only difference if you're writing bytes instead of a string is changing:
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(plainText);
}
to
csEncrypt.Write(plainText);
Either way, for the actual crypto you'd use AesCryptoServiceProvider, not RSACryptoServiceProvider, which is for the top-level authentication and encryption of keys/passwords that get passed to AES and similar algorithms. It's rarely used to encrypt whole files, and is dreadfully slow when you do.
I'm enclosing a program I wrote to help you along the way. It is very effective and will encrypt whatever you want. I left notes in it to answer all your questions. At the bottom, you will see an added class to reduce memory usage of the program. It works rather well, (by 8% -10%) Enjoy. Items needed: *3 buttons (Browse) (Encrypt) (Decrypt) *1 TextBox For the project video, see my link:
Part 1: https://www.youtube.com/watch?v=sVaA2q8ttzQ
Part 2: https://www.youtube.com/watch?v=TyafeBJ53YU
'Created by: Rythorian77 | github.com/rythorian77
Imports System.IO
Imports System.Security.Cryptography
Imports System.Text
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim ramClass As New AESRam()
End Sub
Private Sub Browse_Click(sender As Object, e As EventArgs) Handles Browse.Click
'Allows you to access files from folders
Dim fetchCryptDialog As New OpenFileDialog With {
.CheckFileExists = True,
.InitialDirectory = "C:\",
.Multiselect = False
}
If fetchCryptDialog.ShowDialog = DialogResult.OK Then
TextBox1.Text = fetchCryptDialog.FileName
End If
End Sub
Private Sub Encrypt_Click(sender As Object, e As EventArgs) Handles Encrypt.Click
'File path goes to textbox
Dim Rythorian77 As String = TextBox1.Text
'This password can be whater you want.
Dim password As String = "123456789ABCDEFG!##$%^&*()_+"
'A data type is the characteristic of a variable that determines what kind of data it can hold.
'Data types include those in the following table as well as user-defined types and specific types of objects.
Dim key As Byte() = New Byte(31) {}
'When overridden in a derived class, encodes a set of characters into a sequence of bytes.
Encoding.Default.GetBytes(password).CopyTo(key, 0)
'RijndaelManaged still works but is considered obsolete in todays world so we use AES
'Represents the abstract base class from which all implementations of the Advanced Encryption Standard (AES) must inherit.
'https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.aes?view=net-6.0
Dim aes As New RijndaelManaged() With
{
.Mode = CipherMode.CBC,
.KeySize = 256,
.BlockSize = 256,
.Padding = PaddingMode.Zeros
}
'Reads a sequence of bytes from the current memory stream and advances the position within the memory stream by the number of bytes read.
Using mnemonicData As New MemoryStream
'Defines a stream that links data streams to cryptographic transformations.
Using cStream As New CryptoStream(mnemonicData, aes.CreateEncryptor(key, key), CryptoStreamMode.Write)
Dim buffer As Byte() = File.ReadAllBytes(Rythorian77)
cStream.Write(buffer, 0, buffer.Length)
Dim appendBuffer As Byte() = mnemonicData.ToArray()
Dim finalBuffer As Byte() = New Byte(appendBuffer.Length - 1) {}
appendBuffer.CopyTo(finalBuffer, 0)
File.WriteAllBytes(Rythorian77, finalBuffer)
End Using
End Using
End Sub
'The above code notes compliment the same
Private Sub Decrypt_Click(sender As Object, e As EventArgs) Handles Decrypt.Click
Dim Rythorian77 As String = TextBox1.Text
Dim password As String = "123456789ABCDEFG!##$%^&*()_+"
Dim key As Byte() = New Byte(31) {}
Encoding.Default.GetBytes(password).CopyTo(key, 0)
Dim aes As New RijndaelManaged() With
{
.Mode = CipherMode.CBC,
.KeySize = 256,
.BlockSize = 256,
.Padding = PaddingMode.Zeros
}
Using mnemonicData As New MemoryStream
'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> < aes.CreateDecryptor > is the only change from above aes.CreateEncryptor
Using cStream As New CryptoStream(mnemonicData, aes.CreateDecryptor(key, key), CryptoStreamMode.Write)
Dim buffer As Byte() = File.ReadAllBytes(Rythorian77)
cStream.Write(buffer, 0, buffer.Length)
Dim appendBuffer As Byte() = mnemonicData.ToArray()
Dim finalBuffer As Byte() = New Byte(appendBuffer.Length - 1) {}
appendBuffer.CopyTo(finalBuffer, 0)
File.WriteAllBytes(Rythorian77, finalBuffer)
End Using
End Using
End Sub
End Class
**Add this separate Class:**
Imports System.Runtime.InteropServices
Public Class AESRam
'This controls the amount of RAM that your process uses, it doesn't otherwise have any affect on the virtual memory size of your process.
'Sets the minimum and maximum working set sizes for the specified process.
'This will cut memory usage in half.
<DllImport("KERNEL32.DLL", EntryPoint:="SetProcessWorkingSetSize", SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
Friend Shared Function SetProcessWorkingSetSize(pProcess As IntPtr, dwMinimumWorkingSetSize As Integer, dwMaximumWorkingSetSize As Integer) As Boolean
End Function
'Retrieves a pseudo handle for the current process.
<DllImport("KERNEL32.DLL", EntryPoint:="GetCurrentProcess", SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
Friend Shared Function GetCurrentProcess() As IntPtr
End Function
'See Above
Public Sub New()
Dim pHandle As IntPtr = GetCurrentProcess()
SetProcessWorkingSetSize(pHandle, -1, -1)
End Sub
End Class
Related
Summary
I am creating a VB project that requires a user to enter an API key. I do not want to store the API key in plaintext for security purposes, so I am attempting to encrypt it. When changing the source of the "password" from an InputBox to a earlier declared variable, I get an error of System.ArgumentNullException: 'String reference not set to an instance of a String. Parameter name: s', and the execution fails.
Context
For security reasons, I don't want the API key to be stored in Plaintext. Instead, I would like the user to enter it once during the application's initial launch, where it converts the plaintext API key into an encrypted file. To encrypt the file, I don't want the user to enter a password, instead, I have combined a few local variables (User name, MAC address, Install location and Host name.) to serve as a password instead. This means the user shouldn't need to enter it on launch (it will read the encrypted API key from the local file), but if any of the variables change (install location, local username, etc.) then it will error, and the user will need to re-enter the API key.
When I try to change the code from having to type in the password, to using the userKey variable, the program no longer launches correctly. Dim password As String = InputBox("Enter the password:") works fine, but Dim password As String = userKey leads to the above error, despite userKey being defined as a string, it refuses to accept it.
Outcome
I would like to have it so the user only needs to enter the API key once the application is first ever launched. It then takes the machine's data to essentially create a UUID, which is what is used to encrypt the plaintext API key entered by the user. I do not want the user to have to set or remember a password to decrypt the API key. It should be automated, and throw an error if they attempt to login where the automated data doesn't allow successful decryption. It'll wipe the API key and ask the user to re-enter it.
Currently, the code works, but the user has to enter their chosen password each time, which is what I am trying to avoid. I am wanting to fully automate this using the UUID I have generated as userKey which is generated when the form is loaded.
My Code
Please see the current code below. It should be in a working state, but I am unsure as to how I can have it so Dim password as String = userKey doesn't get an error when not having the user actualy typing it.
Conclusion
I am new to VB, so this is a new experiment for me to try and learn VB. Please be gentle - I'd appreciate any tips or tricks on how to best achieve this task and similar. Thanks!
Imports System.IO
Imports Newtonsoft.Json
Imports System.Security.Cryptography
Imports System.Net.NetworkInformation
Imports System.Management
Imports System.Text
Public Class Form1
Public userKey As String
Private posts As List(Of Post)
Public Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim sHostName As String
Dim sUserName As String
Dim mac As String
Dim exePath As String = Application.ExecutablePath()
mac = getMacAddress()
' Get Host Name / Get Computer Name
sHostName = Environ$("computername")
' Get Current User Name
sUserName = Environ$("username")
Dim userKey As String
userKey = sUserName & mac & exePath & sHostName
Label5.Text = userKey
End Sub
Private Sub Button5_Click(sender As Object, e As EventArgs) Handles Button5.Click
Dim plainText As String = InputBox("Enter the plain text:")
Dim password As String = InputBox("Enter the password:")
Dim wrapper As New Simple3Des(password)
Dim cipherText As String = wrapper.EncryptData(plainText)
MsgBox("The cipher text is: " & cipherText)
My.Computer.FileSystem.WriteAllText("cipherText.txt", cipherText, False)
End Sub
Private Sub Button6_Click(sender As Object, e As EventArgs) Handles Button6.Click
Dim cipherText As String = My.Computer.FileSystem.ReadAllText("cipherText.txt")
Dim password As String = InputBox("Enter the password:")
Dim wrapper As New Simple3Des(password)
' DecryptData throws if the wrong password is used.
Try
Dim plainText As String = wrapper.DecryptData(cipherText)
MsgBox("The plain text is: " & plainText)
Catch ex As System.Security.Cryptography.CryptographicException
MsgBox("The data could not be decrypted with the password.")
End Try
End Sub
Function getMacAddress()
Dim nics() As NetworkInterface = NetworkInterface.GetAllNetworkInterfaces()
Return nics(1).GetPhysicalAddress.ToString
End Function
End Class
Public NotInheritable Class Simple3Des
Private TripleDes As New TripleDESCryptoServiceProvider
Private Function TruncateHash(
ByVal key As String,
ByVal length As Integer) As Byte()
Dim sha1 As New SHA1CryptoServiceProvider
' Hash the key.
Dim keyBytes() As Byte =
System.Text.Encoding.Unicode.GetBytes(key)
Dim hash() As Byte = sha1.ComputeHash(keyBytes)
' Truncate or pad the hash.
ReDim Preserve hash(length - 1)
Return hash
End Function
Sub New(ByVal key As String)
' Initialize the crypto provider.
TripleDes.Key = TruncateHash(key, TripleDes.KeySize \ 8)
TripleDes.IV = TruncateHash("", TripleDes.BlockSize \ 8)
End Sub
Public Function EncryptData(
ByVal plaintext As String) As String
' Convert the plaintext string to a byte array.
Dim plaintextBytes() As Byte =
System.Text.Encoding.Unicode.GetBytes(plaintext)
' Create the stream.
Dim ms As New System.IO.MemoryStream
' Create the encoder to write to the stream.
Dim encStream As New CryptoStream(ms,
TripleDes.CreateEncryptor(),
System.Security.Cryptography.CryptoStreamMode.Write)
' Use the crypto stream to write the byte array to the stream.
encStream.Write(plaintextBytes, 0, plaintextBytes.Length)
encStream.FlushFinalBlock()
' Convert the encrypted stream to a printable string.
Return Convert.ToBase64String(ms.ToArray)
End Function
Public Function DecryptData(
ByVal encryptedtext As String) As String
' Convert the encrypted text string to a byte array.
Dim encryptedBytes() As Byte = Convert.FromBase64String(encryptedtext)
' Create the stream.
Dim ms As New System.IO.MemoryStream
' Create the decoder to write to the stream.
Dim decStream As New CryptoStream(ms,
TripleDes.CreateDecryptor(),
System.Security.Cryptography.CryptoStreamMode.Write)
' Use the crypto stream to write the byte array to the stream.
decStream.Write(encryptedBytes, 0, encryptedBytes.Length)
decStream.FlushFinalBlock()
' Convert the plaintext stream to a string.
Return System.Text.Encoding.Unicode.GetString(ms.ToArray)
End Function
End Class
I am trying to write a simple program that finds the public IP for the computer it is being used on. However, I am not sure how to set the text of the TextBox to the IP address that is found. Can anyone help me?
Code:
Imports System.Net
Imports System.Text
Imports System.Text.RegularExpressions
Public Class Form1
Private Function GetMyIP() As IPAddress
Using wc As New WebClient
Return IPAddress.Parse(Encoding.ASCII.GetString(wc.DownloadData("http://tools.feron.it/php/ip.php")))
End Using
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
TextBox1.Text = (GetMyIP())
End Sub
Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
End Sub
End Class
First, you should use Option Strict On. That would point out to you that you need to use
TextBox1.Text = GetMyIP().ToString()
Next, if you examine the headers from that web page you will see it returns the result in UTF-8 encoding, so you should use Encoding.UTF8 instead of Encoding.ASCII. Unfortunately, that still does not work - I will write more on that later.
However, WebClient has a DownloadString method which works well in this case:
Private Function GetMyIP() As IPAddress
Using wc As New WebClient
Dim url = "http://tools.feron.it/php/ip.php"
Dim x = wc.DownloadString(url)
Return IPAddress.Parse(x.Trim())
End Using
End Function
If you still want to use DownloadData, you should examine the returned bytes: you would find that the data you want is preceded by the bytes 0xEF 0xBB 0xBF. I do not know why. This is messing up the string that you want if you download it as an array of bytes.
You could use LINQ to remove the strange bytes:
Private Function GetMyIP() As IPAddress
Using wc As New WebClient
Dim url = "http://tools.feron.it/php/ip.php"
Dim x = wc.DownloadData(url)
Dim y = Encoding.UTF8.GetString(x.Where(Function(b) b < 128).ToArray())
Return IPAddress.Parse(y)
End Using
End Function
(I could have used Encoding.ASCII in there because the bytes over 127 have been removed.)
I am working on improving an old class and I am stuck here.
There is a method like this which works.
Private Database_Bytes As Byte()
Private Base as String = 'Path of Some database file.
Sub New()
If File.Exists(Base) Then
FileOpen(1, Base, OpenMode.Binary, OpenAccess.Read, OpenShare.Shared)
Dim asi As String = Space(LOF(1))
FileGet(1, asi)
FileClose(1)
Database_Bytes = Encoding.Default.GetBytes(asi)
End If
'Operations
End Sub
As FileOpen is old and IO Class offers better performance, I am trying to change it to below method but. I get an IOException if the file is in use by another process.
Private Database_Bytes As Byte()
Private Base as String = 'Path of Some database file.
Sub New()
If File.Exists(Base) Then
Database_Bytes = IO.File.ReadAllBytes(Base)
End If
'Operations
End Sub
How can I avoid the IOException if the file is in use?
As you have found out, File.ReadAllBytes does not allow you to read a file that is in use. However, you can do that with FileStream.Read.
Private Database_Bytes As Byte()
Private Base as String = "Path of Some database file."
Sub New()
If File.Exists(Base) Then
Using fs As New FileStream(Base, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
ReDim Database_Bytes(CInt(fs.Length) - 1)
fs.Read(Database_Bytes, 0, Database_Bytes.Length)
End Using
End If
'Operations
End Sub
The code above assumes that you have Imports System.IO (which seems to be the case based on the code you posted). Otherwise, File.Exists, FileStream, etc. would need to be qualified (e.g. IO.File.Exists, etc.).
I am attempting to create a binary file that is not readable by notepad in windows. This file needs to contain text information. The current code I run is readable in notepad (with a few extra characters here and there, but still human readable). Any assistance is greatly appreciated.
Using writer As BinaryWriter = New BinaryWriter(File.Open("file.bin", FileMode.Create))
writer.Write(rtbWriter.Text)
End Using
All files can be read by notepad - whether it is binary or not. If you don't want the text to be readable (or to be more accurate - understandable), consider using encryption.
EDIT: For an introduction on how to use encryption, see the link below to see how to use the 3DES cryptographic service provider in VB.NET:
simple encrypting / decrypting in VB.Net
*A more sophisticated approach would chain together the File Stream and Crypto Stream...
...but here's a very simple example showing how to encrypt/decrypt individual strings so you have something to play with and learn from:
Imports System.IO
Imports System.Text
Imports System.Security.Cryptography
Public Class Form1
Private Key As String = "SomeRandomKeyThatIsHardCoded"
Private data As New List(Of String)
Private DataFileName As String = System.IO.Path.Combine(My.Computer.FileSystem.SpecialDirectories.MyDocuments, "SomeFile.txt")
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' Some data to play with:
data.Add("User X, Access Y")
data.Add("User Y, Access Z")
data.Add("User Z, Access A")
ListBox1.DataSource = data
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' Write out each entry in encrypted form:
Using SW As New StreamWriter(DataFileName, False)
For Each entry As String In data
SW.WriteLine(Crypto.Encrypt(entry, Key))
Next
End Using
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
data.Clear()
ListBox1.DataSource = Nothing
' Read each encrypted line and decrypt it:
Using SR As New System.IO.StreamReader(DataFileName)
While Not SR.EndOfStream
data.Add(Crypto.Decrypt(SR.ReadLine, Key))
End While
End Using
ListBox1.DataSource = data
End Sub
End Class
Public Class Crypto
Private Shared DES As New TripleDESCryptoServiceProvider
Private Shared MD5 As New MD5CryptoServiceProvider
Public Shared Function MD5Hash(ByVal value As String) As Byte()
Return MD5.ComputeHash(ASCIIEncoding.ASCII.GetBytes(value))
End Function
Public Shared Function Encrypt(ByVal stringToEncrypt As String, ByVal key As String) As String
DES.Key = Crypto.MD5Hash(key)
DES.Mode = CipherMode.ECB
Dim Buffer As Byte() = ASCIIEncoding.ASCII.GetBytes(stringToEncrypt)
Return Convert.ToBase64String(DES.CreateEncryptor().TransformFinalBlock(Buffer, 0, Buffer.Length))
End Function
Public Shared Function Decrypt(ByVal encryptedString As String, ByVal key As String) As String
Try
DES.Key = Crypto.MD5Hash(key)
DES.Mode = CipherMode.ECB
Dim Buffer As Byte() = Convert.FromBase64String(encryptedString)
Return ASCIIEncoding.ASCII.GetString(DES.CreateDecryptor().TransformFinalBlock(Buffer, 0, Buffer.Length))
Catch ex As Exception
Return ""
End Try
End Function
End Class
I have the following:
Public Interface INorthwindSvc
<OperationContract()>
<WebGet(UriTemplate:="EmployeePictureBytes?id={EmpId}")>
Function GetEmployeePictureBytesById(ByVal EmpId As String) As Byte()
End Interface
I got the method implemented (using EF 4.0) as follows:
Public Function GetEmployeePictureBytesById(ByVal EmpId As String) As Byte() Implements INorthwindSvc.GetEmployeePictureBytesById
Dim ctxt As New NorthwindEntities
Dim q = From c In ctxt.Employees
Where c.EmployeeID = EmpId
Select c.Photo
Return q.FirstOrDefault
End Function
I am able to receive bytes when I access the operation from browser. If I try to access the same using Win Client as follows (an error occurs as shown inline):
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim o As New WebClient
Dim b() As Byte = o.DownloadData(New Uri("http://localhost:8732/WcfWebHttpSvcLib/rest/EmployeePictureBytes?id=2"))
Dim ms As New MemoryStream()
ms.Write(b, 0, b.Length)
Dim original As New System.Drawing.Bitmap(ms) 'error: parameter is not valid
End Sub
I also tried the same using Image.FromStream. But, still no luck.
Can anyone help me on this?
thanks
You're not rewinding the memorystream after writing to it, so trying to read from it will fail That said, even that's unnecessary, as you could just write:
im ms As New MemoryStream(b)
' now call FromStream