diff --git a/package.json b/package.json
index faebf6d9918d88c76836b7903b0324e148cd68ab..37c5d1badcb1f933558b3a7d035402525bc72da3 100644
--- a/package.json
+++ b/package.json
@@ -37,8 +37,8 @@
 	"type": "module",
 	"packageManager": "pnpm@9.7.0+sha512.dc09430156b427f5ecfc79888899e1c39d2d690f004be70e05230b72cb173d96839587545d09429b55ac3c429c801b4dc3c0e002f653830a420fa2dd4e3cf9cf",
 	"dependencies": {
-		"cryptr": "^6.3.0",
 		"formsnap": "^1.0.1",
+		"msgpackr": "^1.11.0",
 		"sveltekit-superforms": "^2.17.0",
 		"zod": "^3.23.8"
 	}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cf87dd6be214b5c697d0be425d664369390e9308..5e7542c01cb3c332322bf58d95a5b8cbfa068e7a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,12 +8,12 @@ importers:
 
   .:
     dependencies:
-      cryptr:
-        specifier: ^6.3.0
-        version: 6.3.0
       formsnap:
         specifier: ^1.0.1
         version: 1.0.1(svelte@4.2.18)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.24(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.4.2(@types/node@22.5.0)))(svelte@4.2.18)(vite@5.4.2(@types/node@22.5.0)))(svelte@4.2.18))
+      msgpackr:
+        specifier: ^1.11.0
+        version: 1.11.0
       sveltekit-superforms:
         specifier: ^2.17.0
         version: 2.17.0(@sveltejs/kit@2.5.24(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.4.2(@types/node@22.5.0)))(svelte@4.2.18)(vite@5.4.2(@types/node@22.5.0)))(svelte@4.2.18)
@@ -541,6 +541,36 @@ packages:
     resolution: {integrity: sha512-uDBFBvCYUT4UaZZKv7gJejQvbrOp4YyI1S0Z92DPiMbyLq0DPDXz3Lt2ZqUZKlQrinBX+W1TO6w0RudEX6Q6WA==}
     engines: {node: ^16.14 || >=18}
 
+  '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
+    resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3':
+    resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==}
+    cpu: [x64]
+    os: [darwin]
+
+  '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3':
+    resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==}
+    cpu: [arm64]
+    os: [linux]
+
+  '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3':
+    resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==}
+    cpu: [arm]
+    os: [linux]
+
+  '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3':
+    resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==}
+    cpu: [x64]
+    os: [linux]
+
+  '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
+    resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==}
+    cpu: [x64]
+    os: [win32]
+
   '@nodelib/fs.scandir@2.1.5':
     resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
     engines: {node: '>= 8'}
@@ -994,9 +1024,6 @@ packages:
     resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
     engines: {node: '>= 8'}
 
-  cryptr@6.3.0:
-    resolution: {integrity: sha512-TA4byAuorT8qooU9H8YJhBwnqD151i1rcauHfJ3Divg6HmukHB2AYMp0hmjv2873J2alr4t15QqC7zAnWFrtfQ==}
-
   css-tree@2.3.1:
     resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==}
     engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
@@ -1037,6 +1064,10 @@ packages:
     resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
     engines: {node: '>=8'}
 
+  detect-libc@2.0.3:
+    resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
+    engines: {node: '>=8'}
+
   devalue@4.3.3:
     resolution: {integrity: sha512-UH8EL6H2ifcY8TbD2QsxwCC/pr5xSwPvv85LrLXVihmHVC3T3YqTCIwnR5ak0yO1KYqlxrPVOA/JVZJYPy2ATg==}
 
@@ -1545,6 +1576,13 @@ packages:
   ms@2.1.2:
     resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
 
+  msgpackr-extract@3.0.3:
+    resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==}
+    hasBin: true
+
+  msgpackr@1.11.0:
+    resolution: {integrity: sha512-I8qXuuALqJe5laEBYoFykChhSXLikZmUhccjGsPuSJ/7uPip2TJ7lwdIQwWSAi0jGZDXv4WOP8Qg65QZRuXxXw==}
+
   mz@2.7.0:
     resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
 
@@ -1569,6 +1607,10 @@ packages:
     resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
     engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
 
+  node-gyp-build-optional-packages@5.2.2:
+    resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==}
+    hasBin: true
+
   node-releases@2.0.18:
     resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==}
 
@@ -2644,6 +2686,24 @@ snapshots:
     dependencies:
       esm-env: 1.0.0
 
+  '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
+    optional: true
+
+  '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3':
+    optional: true
+
+  '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3':
+    optional: true
+
+  '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3':
+    optional: true
+
+  '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3':
+    optional: true
+
+  '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
+    optional: true
+
   '@nodelib/fs.scandir@2.1.5':
     dependencies:
       '@nodelib/fs.stat': 2.0.5
@@ -3130,8 +3190,6 @@ snapshots:
       shebang-command: 2.0.0
       which: 2.0.2
 
-  cryptr@6.3.0: {}
-
   css-tree@2.3.1:
     dependencies:
       mdn-data: 2.0.30
@@ -3156,6 +3214,9 @@ snapshots:
 
   detect-indent@6.1.0: {}
 
+  detect-libc@2.0.3:
+    optional: true
+
   devalue@4.3.3: {}
 
   devalue@5.0.0: {}
@@ -3756,6 +3817,22 @@ snapshots:
 
   ms@2.1.2: {}
 
+  msgpackr-extract@3.0.3:
+    dependencies:
+      node-gyp-build-optional-packages: 5.2.2
+    optionalDependencies:
+      '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3
+      '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3
+      '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3
+      '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3
+      '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3
+      '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3
+    optional: true
+
+  msgpackr@1.11.0:
+    optionalDependencies:
+      msgpackr-extract: 3.0.3
+
   mz@2.7.0:
     dependencies:
       any-promise: 1.3.0
@@ -3776,6 +3853,11 @@ snapshots:
       fetch-blob: 3.2.0
       formdata-polyfill: 4.0.10
 
+  node-gyp-build-optional-packages@5.2.2:
+    dependencies:
+      detect-libc: 2.0.3
+    optional: true
+
   node-releases@2.0.18: {}
 
   normalize-path@3.0.0: {}
diff --git a/src/lib/cookie.ts b/src/lib/cookie.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c26031cfcd620ea034d251333a813860f5c79db3
--- /dev/null
+++ b/src/lib/cookie.ts
@@ -0,0 +1,31 @@
+import type { Cookies } from '@sveltejs/kit';
+import Cryptr from './crypto';
+import env from '$lib/env';
+import type { Schema } from 'zod';
+import { encode, decode } from 'msgpackr';
+
+const cryptr = new Cryptr(env.COOKIE_SECRET);
+
+export function secureCookie<T>(cookies: Cookies, schema: Schema<T>, name: string) {
+	function read(): T | null {
+		const cookie = cookies.get(name);
+		if (!cookie) return null;
+
+		try {
+			const decryptedBuffer = cryptr.decrypt(cookie);
+			const json = decode(decryptedBuffer);
+			const data = schema.parse(json);
+			return data;
+		} catch {
+			return null;
+		}
+	}
+
+	function write(data: T) {
+		const compactJson = encode(data);
+		const encryptedBuffer = cryptr.encrypt(compactJson);
+		cookies.set(name, encryptedBuffer, { path: '/' });
+	}
+
+	return { read, write };
+}
diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts
new file mode 100644
index 0000000000000000000000000000000000000000..537200c2df1b0723df22a99326ecaa047493dab1
--- /dev/null
+++ b/src/lib/crypto.ts
@@ -0,0 +1,50 @@
+import crypto from 'crypto';
+
+// Inspiré de https://github.com/MauriceButler/cryptr/blob/master/index.js
+export default class Cryptr {
+	algorithm = 'aes-256-gcm' as const;
+	encoding = 'base64' as const;
+	pbkdf2Iterations = 100000;
+	saltLength = 64;
+	ivLength = 16;
+	tagLength = 16;
+	tagPosition = this.saltLength + this.ivLength;
+	encryptedPosition = this.tagPosition + this.tagLength;
+
+	constructor(public secret: string) {}
+
+	getKey(salt: crypto.BinaryLike) {
+		return crypto.pbkdf2Sync(this.secret, salt, this.pbkdf2Iterations, 32, 'sha512');
+	}
+
+	encrypt(value: crypto.BinaryLike) {
+		const iv = crypto.randomBytes(this.ivLength);
+		const salt = crypto.randomBytes(this.saltLength);
+
+		const key = this.getKey(salt);
+
+		const cipher = crypto.createCipheriv(this.algorithm, key, iv);
+		const encrypted = Buffer.concat([cipher.update(value), cipher.final()]);
+
+		const tag = cipher.getAuthTag();
+
+		return Buffer.concat([salt, iv, tag, encrypted]).toString(this.encoding);
+	}
+
+	decrypt(value: string) {
+		const stringValue = Buffer.from(String(value), this.encoding);
+
+		const salt = stringValue.subarray(0, this.saltLength);
+		const iv = stringValue.subarray(this.saltLength, this.tagPosition);
+		const tag = stringValue.subarray(this.tagPosition, this.encryptedPosition);
+		const encrypted = stringValue.subarray(this.encryptedPosition);
+
+		const key = this.getKey(salt);
+
+		const decipher = crypto.createDecipheriv(this.algorithm, key, iv);
+
+		decipher.setAuthTag(tag);
+
+		return Buffer.concat([decipher.update(encrypted), decipher.final()]);
+	}
+}
diff --git a/src/lib/env.ts b/src/lib/env.ts
index ae984638a80a387df668c6f4e0b2fb4ceff194c6..53723ea231b71ec29d86cea8fb4cf6d4113f5d62 100644
--- a/src/lib/env.ts
+++ b/src/lib/env.ts
@@ -17,4 +17,4 @@ function ensureEnv<K extends readonly string[]>(keys: K): RecordFromKeys<K> {
 	return cleanEnv;
 }
 
-export default ensureEnv(['API_ORIGIN', 'API_TOKEN'] as const);
+export default ensureEnv(['API_ORIGIN', 'API_TOKEN', 'COOKIE_SECRET'] as const);
diff --git a/src/lib/game.ts b/src/lib/game.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f93d0bb0943c56b2a136a6ef180eacbd618bd420
--- /dev/null
+++ b/src/lib/game.ts
@@ -0,0 +1,36 @@
+import type { Cookies } from '@sveltejs/kit';
+import { secureCookie } from './cookie';
+import { z } from 'zod';
+
+export const gameStateSchema = z.object({
+	history: z.array(z.string()),
+	step: z.number(),
+	points: z.number(),
+	options: z.array(z.string()),
+	state: z.enum(['playing', 'solution', 'next']),
+	solution: z.string()
+});
+
+export type GameState = z.infer<typeof gameStateSchema>;
+
+const defaultGameState: GameState = {
+	state: 'next',
+	history: [],
+	step: 0,
+	points: 0,
+	options: [],
+	solution: ''
+};
+
+export function loadGameState(cookies: Cookies) {
+	const cookie = secureCookie(cookies, gameStateSchema, 'qui-est-ce');
+
+	const gameState = cookie.read() ?? defaultGameState;
+
+	return {
+		gameState,
+		saveGame() {
+			cookie.write(gameState);
+		}
+	};
+}
diff --git a/src/routes/wip/+page.server.ts b/src/routes/wip/+page.server.ts
index 588151c905c0636630e7513be7e03724bd3e5473..7bfea6d7d486f74a09d2f09524856a4eef2ce160 100644
--- a/src/routes/wip/+page.server.ts
+++ b/src/routes/wip/+page.server.ts
@@ -1,49 +1,19 @@
 import { superValidate } from 'sveltekit-superforms';
 import { zod } from 'sveltekit-superforms/adapters';
-import { gameStateSchema, schema } from './schema';
-import type { GameState } from './schema';
-import { fail, redirect, type Cookies } from '@sveltejs/kit';
+import { schema } from './schema';
+import { fail } from '@sveltejs/kit';
 import { GetPromotionStore, UserDetailsStore } from '$houdini';
 import { pageIterator } from '$lib/graphql/query';
 import { getRandomItems } from '$lib/utils';
-import Cryptr from 'cryptr';
+import { loadGameState } from '$lib/game';
 
 type Option = {
 	value: string;
 	label: string;
 };
 
-const cryptr = new Cryptr('myTotallySecretKey');
-
-function loadGameState(cookies: Cookies): GameState | null {
-	const cookie = cookies.get('qui-est-ce');
-	if (!cookie) return null;
-
-	const decryptedString = cryptr.decrypt(cookie);
-	const gameState = gameStateSchema.safeParse(JSON.parse(decryptedString));
-
-	if (!gameState.success) return null;
-	return gameState.data;
-}
-
-function saveGameState(gameState: GameState, cookies: Cookies) {
-	const encryptedString = cryptr.encrypt(JSON.stringify(gameState));
-	cookies.set('qui-est-ce', encryptedString, { path: '/' });
-}
-
 export async function load(event) {
-	let gameState = loadGameState(event.cookies);
-
-	if (gameState === null) {
-		gameState = {
-			state: 'next',
-			history: [],
-			step: 0,
-			points: 0,
-			options: [],
-			solution: ''
-		};
-	}
+	let { gameState, saveGame } = loadGameState(event.cookies);
 
 	if (gameState.state === 'next') {
 		const pagination = pageIterator(event, GetPromotionStore, { promotion: 2023 });
@@ -70,7 +40,7 @@ export async function load(event) {
 
 	console.log(gameState);
 
-	saveGameState(gameState, event.cookies);
+	saveGame();
 
 	const photo = users.find((node) => node.id === gameState.solution)?.photo!;
 
@@ -88,18 +58,13 @@ export const actions = {
 			return fail(400, { form });
 		}
 
-		let gameState = loadGameState(event.cookies);
-
-		if (gameState === null) {
-			return redirect(307, '/wip');
-		}
+		let { gameState, saveGame } = loadGameState(event.cookies);
 
 		if (form.data.choice === gameState.solution) {
 			gameState.points += 10;
 		}
 		gameState.history.push(gameState.solution);
-
-		saveGameState(gameState, event.cookies);
+		saveGame();
 
 		return {
 			form,
@@ -110,14 +75,10 @@ export const actions = {
 	async next(event) {
 		const form = await superValidate(zod(schema));
 
-		let gameState = loadGameState(event.cookies);
-
-		if (gameState === null) {
-			return redirect(307, '/wip');
-		}
+		let { gameState, saveGame } = loadGameState(event.cookies);
 
 		gameState.state = 'next';
-		saveGameState(gameState, event.cookies);
+		saveGame();
 
 		return { form };
 	}
diff --git a/src/routes/wip/schema.ts b/src/routes/wip/schema.ts
index 8f04be14e2b62c9d9993694b67063b1c28a0eb9c..6e7360b7c2a672d7ecc169ad556f6e8973623096 100644
--- a/src/routes/wip/schema.ts
+++ b/src/routes/wip/schema.ts
@@ -3,14 +3,3 @@ import { z } from 'zod';
 export const schema = z.object({
 	choice: z.string()
 });
-
-export const gameStateSchema = z.object({
-	history: z.array(z.string()),
-	step: z.number(),
-	points: z.number(),
-	options: z.array(z.string()),
-	state: z.enum(['playing', 'solution', 'next']),
-	solution: z.string()
-});
-
-export type GameState = z.infer<typeof gameStateSchema>;