Encriptar y desencriptar dinámicamente el código es la parte fácil - incluso en código máquina, todo es sólo un montón de bytes; puedes hacer lo que quieras con ellos (siempre y cuando notifiques al sistema operativo de tus intenciones). Por ejemplo, mira esto - Vladislav Zorov's respuesta a C (lenguaje de programación): ¿Puedes escribir un programa en C para demostrar un código auto modificable?
Ocultar la clave de cifrado es mucho más difícil. Hay esquemas criptográficos especiales para eso, llamados criptografía de caja blanca - la idea es que la clave no se almacena como datos, sino que está implícita en la estructura de una pieza compleja de código.
Por supuesto, todo eso no sirve de nada si se puede ejecutar el programa hasta que se haya descifrado a sí mismo, y luego volcar todo. Así que tiene que descifrar, ejecutar y volver a cifrar continuamente.
Como impedimento añadido, puedes inventarte tu propia CPU, con un conjunto de instrucciones diseñado para ser especialmente obtuso. Y luego simplemente compilar el programa para eso, y hacer que se ejecute en una máquina virtual.
También habrá mecanismos para detectar la ejecución en un depurador o máquina virtual, para dificultar el análisis de código dinámico.
Por supuesto, nada de lo anterior funciona realmente - sólo hace que la ingeniería inversa sea más lenta y dolorosa. Si hubiera una solución, tendríamos un DRM que funcionara, y está claro que no lo tenemos.
Supongo que no te refieres realmente al "código fuente", sino sólo al "código". El código fuente generalmente no se incluye con la aplicación, excepto en el software libre/de código abierto, donde impedir que la gente lo vea es exactamente lo contrario de lo que se intenta hacer.