The simple difference is:
custom properties have to be defined by script first, then they are available to all the instances of the type of object they were defined for. Plenty of data types available (arrays, bools, ..)
ID property can be assigned to single instance of object. There are less data types available.
Both will be saved with the file. Also both will show in the UI, the custom prop will show as "API Defined":
When you open such file and don't define the custom property, it will be converted into ID property:
When you run bpy.types.Scene.prop = bpy.props.IntProperty()
it will turn back into custom property.
There is a bug nowin 2.77a however. If you happen to define bpy.types.Scene.prop
you are not able to create any ID properties for scenes - the default name for them is "prop". I will report thisThis was reported and fixed for further releases.
Last very important thing - as custom properties are heavily used to store values for rigs
- Pose Libraries do not store custom prop data automatically, but they will store ID properties values. Custom prop can be keyframed or driven by driver the same way is ID property can though.