In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-23 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This "sample analysis of Android Room database encryption" is not understood by most people except programmers. Today, in order to make you better understand the "sample analysis of Android Room database encryption", Xiaobian summed up the following content, which has a certain reference value. The detailed steps are clear and the details are handled properly. I hope you can get something through this article. Let's take a look at the specific content.
I. demand background
The SQLite that comes with the Android platform has a fatal flaw: it does not support encryption. This causes the data stored in SQLite to be viewed by anyone using any text editor. If it is ordinary data, but when it comes to some account passwords, or chat content, our application will face serious security vulnerabilities.
2. Encryption scheme
1. Encrypt the data before storage and decrypt the data after loading. This method is probably the easiest to think of, and it can not be said to be bad, but it is somewhat cumbersome. If the project has special needs, you may also need to show and encrypt the database.
2. Encrypt the whole file of the database, the advantage is that there is no need to encrypt the data before insertion, and there is no need to decrypt the data after querying. The more famous third-party library is SQLCipher, which uses the way to encrypt database files by entering a password when you open the database, and there is no difference in more normal operation after that.
Third, the implementation of Hook Room
As mentioned earlier, one of the more complicated ways of encryption is that it needs to be encrypted before the data is stored and decrypted after retrieving the data, so is there a way to automatically encrypt and decrypt the data in the process of Room operating the database? the answer is yes.
The compiled code for Dao looks like this:
@ Overridepublic long saveCache (final CacheTest cache) {_ _ db.assertNotSuspendingTransaction (); _ _ db.beginTransaction (); try {/ / Core code, binding data long _ result = _ _ insertionAdapterOfCacheTest.insertAndReturnId (cache); _ _ db.setTransactionSuccessful (); return _ result;} finally {_ db.endTransaction ();}}
_ _ insertionAdapterOfCacheTest is an anonymous inner class created in the constructor of CacheDaoTest_Impl, which implements the bind method
Public CacheDaoTest_Impl (RoomDatabase _ _ db) {this.__db = _ _ db; this.__insertionAdapterOfCacheTest = new EntityInsertionAdapter (_ _ db) {@ Override public String createQuery () {return "INSERT OR REPLACE INTO `table_ cache` (`key`, `name`) VALUES (?,?)";} @ Override public void bind (SupportSQLiteStatement stmt, CacheTest value) {if (value.getKey () = = null) {stmt.bindNull (1) } else {stmt.bindString (1, value.getKey ());} if (value.getName () = = null) {stmt.bindNull (2);} else {stmt.bindString (2, value.getName ());};}
Students who are not clear about SQLiteStatement can do it on Baidu. To put it simply, it represents a sql statement. The bind method is to bind the parameters required by the sql statement. The question now is whether we can customize a SupportSQLiteStatement and encrypt the parameters during bind.
Let's take a look at the process of creating SupportSQLiteStatement.
Public SupportSQLiteStatement acquire () {assertNotMainThread (); return getStmt (mLock.compareAndSet (false, true));} private SupportSQLiteStatement getStmt (boolean canUseCached) {final SupportSQLiteStatement stmt; / / Code has been deleted stmt = createNewStatement (); return stmt;} kotlin private SupportSQLiteStatement createNewStatement () {String query = createQuery (); return mDatabase.compileStatement (query);}
You can see that SupportSQLiteStatement finally comes from the compileStatement method of RoomDataBase, which gives us an interface to hook. All we have to do is customize a SupportSQLiteStatement class to proxy the original SupportSQLiteStatement.
Encoder is used to encrypt data.
After encrypting the data, what's left is to decrypt the data. Where do we need Hook to decrypt the data?
We know that the data returned by database retrieval is generally transmitted to the user through Cursor, so here we can decrypt the data through the Cursor returned by the proxy database.
@ Database (entities = [CacheTest::class], version = 3) abstract class TestDb: RoomDatabase () {abstract fun testDao (): CacheDaoTest companion object {val MIGRATION_2_1: Migration = object: Migration (2,1) {override fun migrate (database: SupportSQLiteDatabase) {} val MIGRATION_2_3: Migration = object: Migration (2 3) {override fun migrate (database: SupportSQLiteDatabase) {} val MIGRATION_3_4: Migration = object: Migration (3) override fun migrate (database: SupportSQLiteDatabase) {} val MIGRATION_2_4: Migration = object: Migration (2 4) {override fun migrate (database: SupportSQLiteDatabase) {}} privateval encoder: IEncode = TestEncoder () override fun query (query: SupportSQLiteQuery): Cursor {var cusrosr = super.query (query) println ("start query 1") return DencodeCursor (cusrosr, encoder)} override fun query (query: String, args: Array?): Cursor {var cusrosr = super.query (query Args) println ("start query 2") return DencodeCursor (cusrosr, encoder)} override fun query (query: SupportSQLiteQuery, signal: CancellationSignal?): Cursor {println ("start query 3") return DencodeCursor (super.query (query, signal), encoder)}}
What we rewrite RoomDatabase here is the query method, which proxies the original Cursor.
Class DencodeCursor (val delete: Cursor, val encoder: IEncode): Cursor {/ / Code has been deleted override fun getString (columnIndex: Int): String {return encoder.decodeString (delete.getString (columnIndex))}}
As above, the final encryption and decryption are all hook in the middle of the Room framework. But this has two drawbacks.
The type of data can not be changed in the process of encryption and decryption, that is, integers must be integers after encryption, and integers must also be integers after decryption. At the same time, some fields may not need to be encrypted or decrypted, such as the self-growing integer primary key. In fact, this way is also easier to solve, you can specify that key is an integer, and the rest of the data is a string. In this way, all tree digital types of data do not need to participate in the process of encryption and decryption.
The parameters of sql and must be dynamically bound and not statically specified in the sql statement.
@ Query ("select * from table_cache where `key` =: primaryKey") fun getCache (primaryKey: String): LiveData@Query ("select * from table_cache where `key` = '123'") fun getCache (): LiveData IV, SQLCipher method
SQLCipher rewrote its own code modeled on the official architecture. Various database-related classes provided by the government also exist in SQLCipher and have the same names except for different package names.
The combination of SQLCipher and Room is similar to the situation above, and is also implemented by proxy. Since the class required by Room is not consistent with the class package name provided by SQLCipher, you need to proxy the class provided by SQLCipher and pass it to the Room schema for use.
Fun init (context: Context) {val mDataBase1 = Room.databaseBuilder (context.applicationContext, TestDb::class.java, "user_login_info_db") .openHelperFactory (SafeHelperFactory ("" .toByteArray () .build ()}
Here the main need to customize a SupportSQLiteOpenHelper.Factory that is SafeHelperFactory this SafeHelperFactory is completely modeled on the Room architecture default Factory that is FrameworkSQLiteOpenHelperFactory implementation. The main difference is that the user creates a SQLiteOpenHelper to open the database. The main difference is that the custom Facttory requires a password for encryption and decryption.
We first need to define our own OpenHelperFactory
Public class SafeHelperFactory implements SupportSQLiteOpenHelper.Factory {public static final String POST_KEY_SQL_MIGRATE = "PRAGMA cipher_migrate;"; public static final String POST_KEY_SQL_V3 = "PRAGMA cipher_compatibility = 3;"; final private byte [] passphrase; final private Options options; public SafeHelperFactory (byte [] passphrase, Options options) {this.passphrase = passphrase; this.options = options } / * * {@ inheritDoc} * / @ Override public SupportSQLiteOpenHelper create (SupportSQLiteOpenHelper.Configuration configuration) {return (create (configuration.context, configuration.name, configuration.callback);} public SupportSQLiteOpenHelper create (Context context, String name, SupportSQLiteOpenHelper.Callback callback) {/ / create a Helper return (new Helper (context, name, callback, passphrase, options)) } private void clearPassphrase (char [] passphrase) {for (int I = 0; I < passphrase.length; iTunes +) {passphrase [I] = (byte) 0;}}
SafeHelperFactory's create creates a Helper that implements the SupportSQLiteOpenHelper of the Room framework. In fact, this Helper is a proxy class that is proxied as OpenHelper, and OpenHelper is used to manipulate the database class provided by SQLCipher.
Class Helper implements SupportSQLiteOpenHelper {private final OpenHelper delegate; private final byte [] passphrase; private final boolean clearPassphrase; Helper (Context context, String name, Callback callback, byte [] passphrase, SafeHelperFactory.Options options) {SQLiteDatabase.loadLibs (context); clearPassphrase=options.clearPassphrase; delegate=createDelegate (context, name, callback, options); this.passphrase=passphrase } private OpenHelper createDelegate (Context context, String name, final Callback callback, SafeHelperFactory.Options options) {final Database [] dbRef = new Database [1]; return (new OpenHelper (context, name, dbRef, callback, options);} / * * {@ inheritDoc} * / @ Override synchronized public String getDatabaseName () {return delegate.getDatabaseName () } / * {@ inheritDoc} * / @ Override @ RequiresApi (api = Build.VERSION_CODES.JELLY_BEAN) synchronized public void setWriteAheadLoggingEnabled (boolean enabled) {delegate.setWriteAheadLoggingEnabled (enabled);} @ Override synchronized public SupportSQLiteDatabase getWritableDatabase () {SupportSQLiteDatabase result; try {result = delegate.getWritableSupportDatabase (passphrase);} catch (SQLiteException e) {if (passphrase! = null) {boolean isCleared = true For (byte b: passphrase) {isCleared = isCleared & & (b = = (byte) 0);} if (isCleared) {throw new IllegalStateException ("The passphrase appears to be cleared. This happens by "+" default the first time you use the factory to open a database, so we can remove the "+" cleartext passphrase from memory. If you close the database yourself, please use a "+" fresh SafeHelperFactory to reopen it. If something else (e.g.Room) closed the "+" database, and you cannot control that, use SafeHelperFactory.Options to opt out of "+" the automatic password clearing step. See the project README for more information. ");}} throw e;} if (clearPassphrase & & passphrase! = null) {for (int I = 0; I < passphrase.length; iTunes +) {passphrase [I] = (byte) 0;}} return (result) NOTE: this implementation delegates to getWritableDatabase (), to ensure * that we only need the passphrase once * / @ Override public SupportSQLiteDatabase getReadableDatabase () {return (getWritableDatabase ());} / * * {@ inheritDoc} * / @ Override synchronized public void close () {delegate.close ();} static class OpenHelper extends SQLiteOpenHelper {private final Database [] dbRef; private volatile Callback callback; private volatile boolean migrated }
The class OpenHelper,OpenHelper that actually operates on the database inherits the SQLiteOpenHelper under the net.sqlcipher.database package
Static class OpenHelper extends SQLiteOpenHelper {private final Database [] dbRef; private volatile Callback callback; private volatile boolean migrated OpenHelper (Context context, String name, final Database [] dbRef, final Callback callback, final SafeHelperFactory.Options options) {super (context, name, null, callback.version, new SQLiteDatabaseHook () {@ Override public void preKey (SQLiteDatabase database) {if } @ Override public void postKey (SQLiteDatabase database) {if (options.postKeySqlcards null) {database.rawExecSQL (options.postKeySql);}, new DatabaseErrorHandler () {@ Override public void onCorruption (SQLiteDatabase dbObj) {Database db = dbRef [0] If (db! = null) {callback.onCorruption (db);}); this.dbRef = dbRef; this.callback=callback;} synchronized SupportSQLiteDatabase getWritableSupportDatabase (byte [] passphrase) {migrated = false; SQLiteDatabase db=super.getWritableDatabase (passphrase); if (migrated) {close (); return getWritableSupportDatabase (passphrase) } return getWrappedDb (db);} synchronized Database getWrappedDb (SQLiteDatabase db) {Database wrappedDb = dbRef [0]; if (wrappedDb = = null) {wrappedDb = new Database (db); dbRef [0] = wrappedDb;} return (dbRef [0]) } / * * {@ inheritDoc} * / @ Override public void onCreate (SQLiteDatabase sqLiteDatabase) {callback.onCreate (getWrappedDb (sqLiteDatabase));} / * {@ inheritDoc} * / @ Override public void onUpgrade (SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {migrated = true; callback.onUpgrade (getWrappedDb (sqLiteDatabase), oldVersion, newVersion) } / * * {@ inheritDoc} * / @ Override public void onConfigure (SQLiteDatabase db) {callback.onConfigure (getWrappedDb (db));} / * {@ inheritDoc} * / @ Override public void onDowngrade (SQLiteDatabase db, int oldVersion, int newVersion) {migrated = true; callback.onDowngrade (getWrappedDb (db), oldVersion, newVersion) } / * * {@ inheritDoc} * / @ Override public void onOpen (SQLiteDatabase db) {if (! migrated) {/ / from Google: "if we've migrated, we'll re-open the db so we should not call the callback." Callback.onOpen (getWrappedDb (db));}} / * * {@ inheritDoc} * / @ Override public synchronized void close () {super.close (); dbRef [0] = null;}}
The OpenHelper here is completely modeled on OpenHelper under the framework of Room.
What is Android? Android is a free and open source operating system based on the Linux kernel, mainly used in mobile devices, such as smartphones and tablets, led and developed by Google and the Open Mobile Alliance.
Thank you for your reading. I hope you have a certain understanding of the key issue of "sample Analysis of Android Room Database encryption". The specific usage still needs to be understood by everyone through hands-on experiments. Try it quickly. If you want to read more articles on relevant knowledge points, welcome to follow the industry information channel!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.