Directory / plugin

NEWTIV Migrate

v1.0.0 · by newtiv

Export your users (with their password hashes) and your content to one encrypted file, to move them into a new application without losing anyone.

View on WordPress.org

About this plugin

You are building a new web or mobile application and want to keep the people who already have an account on your WordPress site. NEWTIV Migrate exports, in one file:

  • users: username, email, name, registration date, roles, and optionally their password hashes, profile fields, WooCommerce addresses and any other user meta you name;
  • content: posts, pages, products and custom post types, with categories, tags, custom fields and author;
  • comments (without IP addresses) and the media library list with file addresses.

Because the password hashes are included, your application can check a user’s usual password exactly like WordPress does (phpass for older accounts, bcrypt for WordPress 6.8 and later), then store it in its own format at the first sign-in. Nobody has to reset their password.

Security first

  • The file is always encrypted with a passphrase you choose: key derived with Argon2id, content encrypted with XChaCha20-Poly1305 (libsodium secretstream). Without the passphrase the file cannot be read.
  • The file is streamed to your browser and never stored on the server.
  • Only administrators can export, and they must type their account password again. Five wrong passwords lock the export for 15 minutes.
  • The last 20 exports are listed: who, when, how many records, with or without password hashes.
  • Privacy: the export log stores, for each export, the date, the administrator’s username and IP address. It is deleted when the plugin is uninstalled.
  • Password hashes are optional: untick them for a content-only export.

Large sites: users and content are read in batches of 200. From the command line: wp newtiv-migrate export site.ntvm (see wp help newtiv-migrate export).

This plugin does not connect to any external service.

File format

  1. First line: NTVMIG1.
  2. Second line: a JSON header with the key derivation parameters (kdf.alg = argon2id13, kdf.opslimit, kdf.memlimit, kdf.salt in base64) and the secretstream header (header, base64).
  3. Then frames: a 4-byte big-endian length followed by the ciphertext of one secretstream message. The last frame has the FINAL tag; a file without it is incomplete.

Key: crypto_pwhash(32, passphrase, salt, opslimit, memlimit, ARGON2ID13). Decrypted, the frames form NDJSON, one record per line, each with a type: meta, user, term, post, media, comment, then end with the counts, to check that nothing is missing.

Any libsodium binding can read it: PyNaCl (Python), libsodium-wrappers (Node.js), ext-sodium (PHP).

User records carry pass and pass_format (wp-bcrypt, phpass, bcrypt, md5, argon2). Meta values are exported exactly as stored in WordPress (arrays stay PHP-serialized).

Plugin tags