When the user do the first login your software will ask for a new password.
At this time the DB should be empty.
Now you have a UserPassword and an empty DB.
So the software generate a new random DBdataKey that will be used to encrypt/decrypt the DB contents. To encrypt/decrypt use one of the many known algorithms, like AES or similar one.
Now you create 2 random salt string: PasswordSalt and DBdataKeySalt.
Store PasswordSalt and DBdataKeySalt in the DB as a clear text.
Use a hash function like pbkdf2 (or something like SHA2 or better) with UserPassword and DBdataKeySalt to obtain a DBdataKeyEncryptionKey
Now:
DBdataKey is the key you use to encrypt/decrypt the DB contents
DBdataKeyEncryptionKey is the key you use to encrypt/decrypt the DBdataKey
Use a hash function like pbkdf2 (or something like SHA2 or better) with UserPassword and PasswordSalt to create HashedSaltedUserPassword, a salted-hashed version of the UserPassword, then use DBdataKey to encrypt HasedSaltedUserPassword before storing it in the DB.
From now on you can use DBdataKey to encrypt/decrypt the data to/from the DB.
Now, when the user opens the application, you ask him for the password, then use the inputed password with DBdataKeySalt (which is stored in cleartext) to regenerate the DBdataKeyEncryptionKey, and then use the obtained DBdataKeyEncryptionKey to decrypt the DBdataKey , and so you can use the decrypted DBdataKey to read the DB.
Now use the DBdataKey to decrypt the HashedSaltedUserPassword and compare it with the one you regenerated using the inputed password and the PasswordSalt (which is stored in cleartext) just to double-check that the login information is correct.
If the user inputs the wrong password, when you use the DBdataKeyEncryptionKey to decrypt the DBdataKey the decrypt function will give you a different value (garbage). Using that (wrong) value to decrypt the HashedSaltedUserPassword will result in more garbage values, which won't match the hash.
When the user changes the UserPassword then you only need to decrypt the DBdataKey with the old password and re-encrypt it with the new password, but the DBdataKey won't change, so you won't need to re-encrypt the db contents when the user changes its password.
Reference: I haven't invented anything, this is just a mix of two well known techniques:
- How user-login & password salting/hashing is done
- How on-the-fly disk encryption can be implemented (applied to DB encryption/decryption)